snakeyaml编辑yaml文件并覆盖注释

文章目录

    • 前言
    • 技术积累
    • 实战演示
      • 1、引入maven依赖
      • 2、覆盖注释工具类
      • 3、snakeyaml工具类
      • 4、测试用例
      • 5、测试效果展示
    • 写在最后

前言

最近在做一个动态整合框架的项目,需要根据需求动态组装各个功能模块。其中就涉及到了在application.yaml中加入其他模块的配置,这里我们采用了snakeyaml进行配置信息写入,并采用文件回写保证注释不丢失。

技术积累

SnakeYaml就是用于解析YAML,序列化以及反序列化的第三方框架,解析yml的三方框架有很多,SnakeYaml,jYaml,Jackson等,但是不同的工具功能还是差距较大,比如jYaml就不支持合并。

SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。

SnakeYaml官方地址:http://yaml.org/type/index.html

在这里插入图片描述

实战演示

1、引入maven依赖

<!--yaml编辑-->
<dependency>
  <groupId>org.yaml</groupId>
  <artifactId>snakeyaml</artifactId>
  <version>1.23</version>
</dependency>
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.11.0</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.9</version>
</dependency>

2、覆盖注释工具类

由于snakeyaml在操作文件时候,会先将yaml转为map然后再回写到文件,这个操作会导致注释丢失。
目前有效的方案是将修改前文件注释进行缓存,然后当业务操作完文件后进行注释会写,这样就能够保证注释不会被覆盖。

当然,目前的方案并没有增加新的配置文件注释写入功能,有需要的同学可以自己实现。大概的思路是根据在回写注释的时候根据key将新增的注释写入,此时需要注释多个key相同的情况,故需要判断全链路key以防止重复注释乱序。

package com.example.demo.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * CommentUtils
 * @author senfel
 * @version 1.0
 * @date 2023/12/6 18:20
 */
public class CommentUtils {

    public static final String END = "END###";
    public static final Pattern COMMENT_LINE = Pattern.compile("^\\s*#.*$");
    public static final Pattern BLANK_LINE = Pattern.compile("^\\s*$");
    //带注释的有效行,  使用非贪婪模式匹配有效内容
    public static final Pattern LINE_WITH_COMMENT = Pattern.compile("^(.*?)\\s+#.*$");


    @Data
    @AllArgsConstructor
    public static class Comment {
        private String lineNoComment;
        private String lineWithComment;
        private Integer indexInDuplicates;    // 存在相同行时的索引 (不同key下相同的行, 如 a:\n name: 1  和  b:\n name: 1 )
        private boolean isEndLine() {
            return END.equals(lineNoComment);
        }
    }


    @SneakyThrows
    public static CommentHolder buildCommentHolder(File file) {
        List<Comment> comments = new ArrayList<>();
        Map<String, Integer> duplicatesLineIndex = new HashMap<>();
        CommentHolder holder = new CommentHolder(comments);
        List<String> lines = FileUtils.readLines(file, StandardCharsets.UTF_8);
        // 末尾加个标志, 防止最后的注释丢失
        lines.add(END);
        StringBuilder lastLinesWithComment = new StringBuilder();
        for (String line : lines) {
            if (StringUtils.isBlank(line) || BLANK_LINE.matcher(line).find()) {
                lastLinesWithComment.append(line).append('\n');
                continue;
            }
            // 注释行/空行 都拼接起来
            if (COMMENT_LINE.matcher(line).find()) {
                lastLinesWithComment.append(line).append('\n');
                continue;
            }
            String lineNoComment = line;
            boolean lineWithComment = false;
            // 如果是带注释的行, 也拼接起来, 但是记录非注释的部分
            Matcher matcher = LINE_WITH_COMMENT.matcher(line);
            if (matcher.find()) {
                lineNoComment = matcher.group(1);
                lineWithComment = true;
            }
            // 去除后面的空格
            lineNoComment = lineNoComment.replace("\\s*$", "");
            // 记录下相同行的索引
            Integer idx = duplicatesLineIndex.merge(lineNoComment, 1, Integer::sum);
            // 存在注释内容, 记录
            if (lastLinesWithComment.length() > 0 || lineWithComment) {
                lastLinesWithComment.append(line);
                comments.add(new Comment(lineNoComment, lastLinesWithComment.toString(), idx));
                // 清空注释内容
                lastLinesWithComment = new StringBuilder();
            }
        }

        return holder;
    }


    @AllArgsConstructor
    public static class CommentHolder {
        private List<Comment> comments;

        /**
         * 通过正则表达式移除匹配的行 (防止被移除的行携带注释信息, 导致填充注释时无法正常匹配)
         */
        public void removeLine(String regex) {
            comments.removeIf(comment -> comment.getLineNoComment().matches(regex));
        }

        /**
         * fillComments
         * @param file
         * @author senfel
         * @date 2023/12/7 11:24
         * @return void
         */
        @SneakyThrows
        public void fillComments(File file) {
            if (comments == null || comments.isEmpty()) {
                return;
            }
            if (file == null || !file.exists()) {
                throw new IllegalArgumentException("file is not exist");
            }
            List<String> lines = FileUtils.readLines(file, StandardCharsets.UTF_8);
            Map<String, Integer> duplicatesLineIndex = new HashMap<>();
            int comIdx = 0;
            StringBuilder res = new StringBuilder();
            for (String line : lines) {
                Integer idx = duplicatesLineIndex.merge(line, 1, Integer::sum);
                Comment comment = getOrDefault(comments, comIdx, null);
                if (comment != null &&
                        Objects.equals(line, comment.lineNoComment)
                        && Objects.equals(comment.indexInDuplicates, idx)) {

                    res.append(comment.lineWithComment).append('\n');
                    comIdx++;
                } else {
                    res.append(line).append('\n');
                }
            }
            Comment last = comments.get(comments.size() - 1);
            if (last.isEndLine()) {
                res.append(last.lineWithComment.substring(0, last.lineWithComment.indexOf(END)));
            }
            FileUtils.write(file, res.toString(), StandardCharsets.UTF_8);
        }
    }

    public static <T> T getOrDefault(List<T> vals, int index, T defaultVal) {
        if (vals == null || vals.isEmpty()) {
            return defaultVal;
        }
        if (index >= vals.size()) {
            return defaultVal;
        }
        T v = vals.get(index);
        return v == null ? defaultVal : v;
    }

}

3、snakeyaml工具类

snakeyaml工具类主要作用就是将yaml文件转为map的格式,然后依次进行判断写入或者修改value。

package com.example.demo.utils;

import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * YamlActionUtils 
 * @author senfel
 * @version 1.0
 * @date 2023/12/7 13:48
 */
public class YamlActionUtils {

    /**
     * 配置
     * @author senfel
     * @date 2023/12/7 13:49
     * @return
     */
    private static DumperOptions dumperOptions = new DumperOptions();

    static{
        //设置yaml读取方式为块读取
        dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        dumperOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
        dumperOptions.setPrettyFlow(false);
    }

    /**
     * insertYaml
     * @param key a.b.c
     * @param value
     * @param path
     * @author senfel
     * @date 2023/12/7 10:11
     * @return boolean
     */
    public static boolean insertYaml(String key, Object value, String path) throws Exception {
        Yaml yaml = new Yaml(dumperOptions);
        String[] keys = key.split("\\.");
        int len = keys.length;
        //将属性转为map
        FileInputStream fileInputStream = new FileInputStream(new File(path));
        Map<String, Object> yamlToMap = (Map<String, Object>)yaml.load(fileInputStream);
        Object oldVal = getValue(key, yamlToMap);
        //找到key不再新增
        if (null != oldVal) {
            return true;
        }
        Map<String,Object> temp = yamlToMap;
        for (int i = 0; i < len - 1; i++) {
            if (temp.containsKey(keys[i])) {
                temp = (Map) temp.get(keys[i]);
            } else {
                temp.put(keys[i],new HashMap<String,Object>());
                temp =(Map)temp.get(keys[i]);
            }
            if (i == len - 2) {
                temp.put(keys[i + 1], value);
            }
        }
        try {
            yaml.dump(yamlToMap, new FileWriter(path));
        } catch (Exception e) {
            System.out.println("yaml file insert failed !");
            return false;
        }
        return true;
    }

    /**
     * updateYaml
     * @param paramKey a.b.c
     * @param paramValue
     * @param path
     * @author senfel
     * @date 2023/12/7 10:03
     * @return boolean
     */
    public static boolean updateYaml(String paramKey, Object paramValue,String path) throws Exception{
        Yaml yaml = new Yaml(dumperOptions);
        //yaml文件路径
        String yamlUr = path;
        Map map = null;
        try {
            //将yaml文件加载为map格式
            map = yaml.loadAs(new FileInputStream(yamlUr), Map.class);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //获取当前参数值并且修改
        boolean flag = updateYaml(paramKey, paramValue, map, yamlUr, yaml);
        return flag;
    }


    /**
     * updateYaml
     * @param key a.b.c
     * @param value
     * @param yamlToMap
     * @param path
     * @param yaml
     * @author senfel
     * @date 2023/12/7 10:51
     * @return boolean
     */
    public static boolean updateYaml(String key, Object value, Map<String, Object> yamlToMap, String path, Yaml yaml) {
        Object oldVal = getValue(key, yamlToMap);
        //未找到key 不修改
        if (null == oldVal) {
            return false;
        }
        try {
            Map<String, Object> resultMap = setValue(yamlToMap, key, value);
            if (resultMap != null) {
                yaml.dump(resultMap, new FileWriter(path));
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            System.out.println("yaml file update failed !");
        }
        return false;
    }


    /**
     * getValue
     * @param key a.b.c
     * @param yamlMap
     * @author senfel
     * @date 2023/12/7 10:51
     * @return java.lang.Object
     */
    public static Object getValue(String key, Map<String, Object> yamlMap) {
        String[] keys = key.split("[.]");
        Object o = yamlMap.get(keys[0]);
        if (key.contains(".")) {
            if (o instanceof Map) {
                return getValue(key.substring(key.indexOf(".") + 1), (Map<String, Object>) o);
            } else {
                return null;
            }
        } else {
            return o;
        }
    }


    /**
     * setValue
     * @param map
     * @param key a.b.c
     * @param value
     * @author senfel
     * @date 2023/12/7 9:59
     * @return java.util.Map<java.lang.String, java.lang.Object>
     */
    public static Map<String, Object> setValue(Map<String, Object> map, String key, Object value) {
        String[] keys = key.split("\\.");
        int len = keys.length;
        Map temp = map;
        for (int i = 0; i < len - 1; i++) {
            if (temp.containsKey(keys[i])) {
                temp = (Map) temp.get(keys[i]);
            } else {
                return null;
            }
            if (i == len - 2) {
                temp.put(keys[i + 1], value);
            }
        }
        for (int j = 0; j < len - 1; j++) {
            if (j == len - 1) {
                map.put(keys[j], temp);
            }
        }
        return map;
    }

}

4、测试用例

我们分别新增、修改yaml文件进行测试。

package com.example.demo;

import com.example.demo.utils.CommentUtils;
import com.example.demo.utils.YamlActionUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;

/**
 * YamlActionTest
 * @author senfel
 * @version 1.0
 * @date 2023/12/6 17:55
 */
@SpringBootTest
public class YamlActionTest {

    @Test
    public void addKey() throws Exception{
        String filePath = "D:\\workspace\\demo\\src\\main\\resources\\application.yaml";
        File file = new File(filePath);
        //记录yaml文件的注释信息
        CommentUtils.CommentHolder holder = CommentUtils.buildCommentHolder(file);
        //YamlActionUtils.insertYaml("spring.activemq.broker-url","http://127.0.0.1/test",filePath);
        //YamlActionUtils.insertYaml("spring.activemq.pool.enabled",false,filePath);
        YamlActionUtils.insertYaml("wx.pc.lx.enable",false,filePath);
        //YamlActionUtils.insertYaml("spring.activemq.in-memory",false,filePath);
        //YamlActionUtils.updateYaml("spring.activemq.in-memory",false,filePath);
        //填充注释信息
        holder.fillComments(file);
    }
}

5、测试效果展示

server:
  port: 8888
spring:
  activemq:
    close-timeout: 15 #超时
    broker-url: http://127.0.0.1/test #路径
    pool:
      enabled: false # 是否开启
wx:
  pc:
    lx:
      enable: false

如上所示 wx.pc.lx.enable=false已经写入。

写在最后

snakeyaml编辑yaml文件并覆盖注释还是比较简单,大致就是在操作yaml文件之前对注释进行缓存,操作文件时先将yaml转为map,然后配置数据写入并转换成yaml文件,最后再将注释覆盖在yaml上即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/228977.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows版Minio使用教程(启动,登录,修改密码)

1 、下载安装包 进入官网下载安装包&#xff1a; MinIO | 高性能、支持原生 Kubernetes的对象存储 信任程序安装&#xff0c;就可以啦 2、启动MinIO 第一步&#xff0c;找到minio.exe所在的目录&#xff0c;在地址栏输入cmd进入cmd窗口。 第二步&#xff0c;输入.\minio.exe…

项目状态报告

《项目状态报告》 第1章 当前阶段的工作完成情况 1.1 概述 1.2 各子系统详细进度 第2章 偏差及偏差原因 第3章 偏差纠正措施 第4章 拟进行的变更 第5章 存在的风险及应对计划 第6章 下一阶段主要工作

delphi android打开外部文件,报错android.os.FileUriExposedException解决方法

Android 7.0强制启用了被称作 StrictMode的策略&#xff0c;带来的影响就是你的App对外无法暴露file://类型的URI了。 如果你使用Intent携带这样的URI去打开外部App(比如&#xff1a;打开系统相机拍照)&#xff0c;那么会抛出FileUriExposedException异常。 Delphi 为Android…

Python源码30:海龟画图turtle画紫色的小熊

turtle模块是一个Python的标准库之一&#xff0c;它提供了一个基于Turtle graphics的绘图库。Turtle graphics是一种流行的绘图方式&#xff0c;它通过控制一个小海龟在屏幕上移动来绘制图形。 turtle模块可以让您轻松地创建和控制海龟图形&#xff0c;从而帮助您学习Python编…

yolo目标检测+目标跟踪+车辆计数+车辆分割+车道线变更检测+速度估计

这个项目使用YOLO进行车辆检测&#xff0c;使用SORT&#xff08;简单在线实时跟踪器&#xff09;进行车辆跟踪。该项目实现了以下任务&#xff1a; 车辆计数车道分割车道变更检测速度估计将所有这些详细信息转储到CSV文件中 车辆计数是指在道路上安装相应设备&#xff0c;通过…

盘点11月Sui生态发展,了解Sui的近期成长历程!

11月是Web3的“回暖期”&#xff0c;行业持续展现增长趋势。Sui紧随行业脚步&#xff0c;开展了一系列生态活动。其中历时一个多月的Quest 3游戏活动顺利结束并公布奖励&#xff0c;在多地区成功举办Move和Sui生态黑客松&交流会&#xff0c;还有针对中文社区开发者教育的星…

数据结构与算法(二)分治算法(Java)

目录 一、简介1.1 背景1.2 定义1.3 步骤1.4 时间复杂度 二、经典示例2.1 二分搜索2.2 快速排序2.3 归并排序&#xff08;逆序数&#xff09;2.4 最大子序列和 一、简介 1.1 背景 在学习分治算法之前&#xff0c;我们先来举一个例子。 假如你有一个存钱罐&#xff0c;过年家人…

【AI】以大厂PaaS为例,看人工智能技术方案服务能力的方向(2/2)

目录 三、解决方案 3.1 人脸身份验证 3.2 图像审核&#xff08;暴恐、色情等&#xff09; 3.3 人脸会场签到 3.4 机器人视觉 3.5 视频审核 3.6 电商图文详情生成 3.7 智能客服 接上回&#xff1a; 【AI】以大厂PaaS为例&#xff0c;看人工智能技术方案服务能力的方向&…

【vuex】

vuex 1 理解vuex1.1 vuex是什么1.2 什么时候使用vuex1.3 vuex工作原理图1.4 搭建vuex环境1.5 求和案例1.5.1 vue方式1.5.2 vuex方式 2 vuex核心概念和API2.1 getters配置项2.2 四个map方法的使用2.2.1 mapState方法2.2.2 mapGetters方法2.2.3 mapActions方法2.2.4 mapMutations…

【思路代码详解】2023mathorcup大数据复赛B题妈妈杯高校数学建模挑战赛电商零售商家需求预测及库存优化问题

2023 年 MathorCup 高校数学建模挑战赛——大数据竞赛 赛道 B复赛&#xff1a;电商零售商家需求预测及库存优化问题 问题一 目标&#xff1a;制定补货计划&#xff0c;基于预测销量。 背景&#xff1a;固定库存盘点周期NRT1, 提前期LT3天。 初始条件&#xff1a;所有商品…

temu日本站在哪里入驻

在跨境电商领域中&#xff0c;Temu是拼多多推出的一款备受瞩目的平台。如今&#xff0c;越来越多的商家希望将自己的业务扩展到日本市场&#xff0c;而在Temu日本站上入驻就成为了一个不可忽视的机遇。本文将为您介绍如何在Temu日本站上入驻&#xff0c;并提供一些有用的技巧和…

excel做预测的方法集合

一. LINEST函数 首先&#xff0c;一元线性回归的方程&#xff1a; y a bx 相应的&#xff0c;多元线性回归方程式&#xff1a; y a b1x1 b2x2 … bnxn 这里&#xff1a; y - 因变量即预测值x - 自变量a - 截距b - 斜率 LINEST的可以返回回归方程的 截距(a) 和 斜…

Spring Boot实现接口幂等

Spring Boot实现接口幂等 1、pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http:…

记一次xss通杀挖掘历程

前言 前端时间&#xff0c;要开放一个端口&#xff0c;让我进行一次安全检测&#xff0c;发现的一个漏洞。 经过 访问之后发现是类似一个目录索引的端口。(这里上厚码了哈) 错误案例测试 乱输内容asdasffda之后看了一眼Burp的抓包&#xff0c;抓到的内容是可以发现这是一个…

12.08

1.头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget(); signals:v…

2023五岳杯量子计算挑战赛数学建模思路+模型+代码+论文

赛题思路&#xff1a;12月6日晚开赛后第一时间更新&#xff0c;获取见文末名片 “五岳杯”量子计算挑战赛&#xff0c;是国内专业的量子计算大赛&#xff0c;也是玻色量子首次联合移动云、南方科技大学共同发起的一场“企校联名”的国际竞赛&#xff0c;旨在深度融合“量子计算…

正则表达式(7):转义符

正则表达式&#xff08;7&#xff09;&#xff1a;正则表达式&#xff08;5&#xff09;&#xff1a;转义符 本博文转载自 此处&#xff0c;我们来认识一个常用符号&#xff0c;它就是反斜杠 “\” 反斜杠有什么作用呢&#xff1f;先不着急解释&#xff0c;先来看个小例子。 …

TCP传输层详解(计算机网络复习)

介绍&#xff1a;TCP/IP包含了一系列的协议&#xff0c;也叫TCP/IP协议族&#xff0c;简称TCP/IP。该协议族提供了点对点的连接机制&#xff0c;并将传输数据帧的封装、寻址、传输、路由以及接收方式都予以标准化 TCP/IP的分层模型 在讲TCP/IP协议之前&#xff0c;首先介绍一…

Python列表的排序方法:从基础到高级

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python列表的排序方法&#xff1a;从基础到高级&#xff0c;全文3400字&#xff0c;阅读大约10分钟。 在Python中&#xff0c;列表是一种常用的数据结构&#xff0c;而对列表…

<习题集><LeetCode><链表><61/83/82/86/92>

61. 旋转链表 https://leetcode.cn/problems/rotate-list/ public ListNode rotateRight(ListNode head, int k) {//k等于0&#xff0c;或者head为空&#xff0c;直接返回head&#xff1b;if(k 0 || head null){return head;}//创建last用于记录尾节点&#xff0c;移动last找…