package com.freetmp.mbg.plugin; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.dom4j.*; import org.dom4j.io.SAXReader; import org.mybatis.generator.api.GeneratedXmlFile; import org.mybatis.generator.api.IntrospectedTable; import org.mybatis.generator.api.PluginAdapter; import org.mybatis.generator.api.dom.xml.*; import org.mybatis.generator.api.dom.xml.Attribute; import org.mybatis.generator.api.dom.xml.Document; import org.mybatis.generator.api.dom.xml.Element; import org.mybatis.generator.config.SqlMapGeneratorConfiguration; import org.mybatis.generator.exception.ShellException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import static org.mybatis.generator.internal.util.StringUtility.stringHasValue; import static org.mybatis.generator.internal.util.messages.Messages.getString; /** * Created by pin on 2015/2/10. */ public class XMLMergePlugin extends PluginAdapter { static final Logger log = LoggerFactory.getLogger(XMLMergePlugin.class); public static final String ROOTDIR_NAME = "rootDir"; private String rootDir = "."; public boolean validate(List<String> warnings) { String prop = properties.getProperty(ROOTDIR_NAME); boolean valid = stringHasValue(prop); if (valid) { rootDir = prop; } else { warnings.add(getString("ValidationError.18", "XMLMergePlugin", "rootDir")); } return true; } /** * 从DefaultShellCallback中借用的解析文件夹的函数 * * @param targetProject target project * @param targetPackage target package * @return file instance * @throws ShellException Cannot get infos form environment */ public File getDirectory(String targetProject, String targetPackage) throws ShellException { // targetProject is interpreted as a directory that must exist // // targetPackage is interpreted as a sub directory, but in package // format (with dots instead of slashes). The sub directory will be // created // if it does not already exist File project = new File(targetProject); if (!project.isDirectory()) { throw new ShellException(getString("Warning.9", //$NON-NLS-1$ targetProject)); } StringBuilder sb = new StringBuilder(); StringTokenizer st = new StringTokenizer(targetPackage, "."); //$NON-NLS-1$ while (st.hasMoreTokens()) { sb.append(st.nextToken()); sb.append(File.separatorChar); } File directory = new File(project, sb.toString()); if (!directory.isDirectory()) { boolean rc = directory.mkdirs(); if (!rc) { throw new ShellException(getString("Warning.10", //$NON-NLS-1$ directory.getAbsolutePath())); } } return directory; } /** * 根据xml文件所属的包名获取相应的文件 * * @param packiage package declaration * @param fileName target file name * @throws ShellException Cannot get infos form environment */ private File getTargetFile(String packiage, String fileName) throws ShellException { File root = new File(this.rootDir); if (!root.exists()) { root.mkdirs(); } File directory = getDirectory(rootDir, packiage); File targetFile = new File(directory, fileName); return targetFile; } @Override public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) { SqlMapGeneratorConfiguration smgc = context.getSqlMapGeneratorConfiguration(); try { Document document = (Document) FieldUtils.readDeclaredField(sqlMap, "document", true); File targetFile = getTargetFile(smgc.getTargetPackage(), sqlMap.getFileName()); if (!targetFile.exists()) { // 第一次生成直接使用当前生成的文件 return true; } visitAndMerge(document, targetFile); } catch (ShellException | IOException | IllegalAccessException | DocumentException e) { e.printStackTrace(); } return true; } /** * 访问并合并mapper的xml文件 * * @param document generate xml dom tree * @param targetFile the file for writing xml content * @throws java.io.IOException * @throws org.dom4j.DocumentException */ private void visitAndMerge(final Document document, File targetFile) throws IOException, DocumentException { SAXReader reader = new SAXReader(); reader.setValidation(false); org.dom4j.Document doc = reader.read(targetFile); final org.dom4j.Element rootElement = doc.getRootElement(); rootElement.accept(new VisitorSupport() { @Override public void visit(org.dom4j.Element node) { //根节点的直属子节点 if (node.getParent() == rootElement) { XmlElement xe = findMatchedElementIn(document, node); if (xe == null) { // 新增节点 添加到document中 int index = node.getParent().elements().indexOf(node); xe = transformElement(node); log.info("XmlElement Add ---> " + xe.getName() + " id=" + idValue(node)); document.getRootElement().getElements().add(index, xe); } else { // 合并已经存在的节点 XmlElement nxe = transformElement(node); mergeExistedElement(nxe, xe); log.info("XmlElement Merge ---> " + xe.getName() + " id=" + idValue(node)); } } } @Override public void visit(Comment node) { // mbg 暂不支持xml doc注释节点添加 } }); } /** * 合并已经存在的Xml元素 * * @param src The source xml element for merging * @param dest The dest xml element for merging */ protected void mergeExistedElement(XmlElement src, XmlElement dest) { // 合并属性 List<Attribute> srcAttributes = src.getAttributes(); List<Attribute> destAttributes = dest.getAttributes(); for (Attribute srcAttr : srcAttributes) { Attribute matched = null; for (Attribute destAttr : destAttributes) { if (StringUtils.equals(srcAttr.getName(), destAttr.getName()) && StringUtils.equals(srcAttr.getValue(), destAttr.getValue())) { matched = destAttr; } } // 不存在则添加到目标元素的属性列表中 if (matched == null) { destAttributes.add(srcAttributes.indexOf(srcAttr), srcAttr); } } // 重组子节点 // reformationTheElementChilds(src); // reformationTheElementChilds(dest); // 暂时不做处理 ---留待后续添加 } /** * 重组XML元素的亲子节点,合并相邻的文本节点 * * @param xe The element whose content will be reformatted */ protected void reformationTheElementChilds(XmlElement xe) { List<Element> reformationList = new ArrayList<>(); for (Element element : xe.getElements()) { // 如果是XML元素节点,则直接添加 if (element instanceof XmlElement) { reformationList.add(element); } if (element instanceof TextElement) { int lastIndex = reformationList.size() - 1; TextElement te = (TextElement) element; // 如果当前文本节点之前的一个节点也是文本节点时,将两个文本节点合并成一个替换之前的节点 // 否则直接添加 if (!reformationList.isEmpty() && reformationList.get(lastIndex) instanceof TextElement) { te = (TextElement) reformationList.get(lastIndex); StringBuilder sb = new StringBuilder(); sb.append(te.getContent()).append(((TextElement) element).getContent()); te = new TextElement(sb.toString()); reformationList.remove(lastIndex); } reformationList.add(te); } } // 清空原有的子元素列表,并用重组后的子节点列表填充 xe.getElements().clear(); xe.getElements().addAll(reformationList); } /** * 转换dom4j的element元素,生成mbg的XmlElement元素 * * @param node The dom4j node to be transform * @return The transform result */ protected XmlElement transformElement(org.dom4j.Element node) { XmlElement xe = new XmlElement(node.getName()); // 设置元素的属性 @SuppressWarnings("unchecked") Iterator<org.dom4j.Attribute> iterator = node.attributeIterator(); while (iterator.hasNext()) { org.dom4j.Attribute ab = iterator.next(); xe.addAttribute(new Attribute(ab.getName(), ab.getValue())); } // 深度优先遍历子节点 @SuppressWarnings("unchecked") Iterator<org.dom4j.Node> niter = node.nodeIterator(); while (niter.hasNext()) { org.dom4j.Node n = niter.next(); // 文本节点 if (n.getNodeType() == org.dom4j.Node.TEXT_NODE) { Text text = (Text) n; TextElement te = new TextElement(text.getText().trim()); xe.addElement(te); } // 元素节点 if (n.getNodeType() == org.dom4j.Node.ELEMENT_NODE) { xe.addElement(transformElement((org.dom4j.Element) n)); } // 注释节点 if (n.getNodeType() == org.dom4j.Node.COMMENT_NODE) { TextElement te = new TextElement(n.asXML().trim()); xe.addElement(te); } // CDATA 节点 if (n.getNodeType() == org.dom4j.Node.CDATA_SECTION_NODE) { TextElement te = new TextElement(n.asXML().trim()); xe.addElement(te); } } return xe; } /** * 从MBG生成的DOM文档结构中找到与element代表同一节点的元素对象 * * @param document generate xml dom tree * @param element The dom4j element * @return The xml element correspond to dom4j element */ protected XmlElement findMatchedElementIn(Document document, org.dom4j.Element element) { org.dom4j.Attribute id = element.attribute("id"); String idName = id.getName(); String idValue = id.getValue(); for (Element me : document.getRootElement().getElements()) { if (me instanceof XmlElement) { XmlElement xe = (XmlElement) me; for (Attribute ab : xe.getAttributes()) { if (StringUtils.equals(idName, ab.getName()) && StringUtils.equals(idValue, ab.getValue())) { return xe; } } } } return null; } protected String idValue(org.dom4j.Element element) { org.dom4j.Attribute id = element.attribute("id"); if (id != null) { return id.getValue(); } else { return ""; } } }