snakeyaml编辑yaml文件并覆盖注释

news2024/11/18 9:40:40

文章目录

    • 前言
    • 技术积累
    • 实战演示
      • 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上即可。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kubernetes(K8s 1.27.x) 快速上手+实践,无废话纯享版

文章目录 1 基础知识1.1 K8s 有用么&#xff1f;1.2 K8s 是什么&#xff1f;1.3 k8s 部署方式1.4 k8s 环境解析 2 环境部署2.1 基础环境配置2.2 容器环境操作2.3 cri环境操作2.4 harbor仓库操作2.5 k8s集群初始化2.6 k8s环境收尾操作 3 应用部署3.1 应用管理解读3.2 应用部署实…

浏览器提示不安全

当我们使用浏览器访问一个网站时&#xff0c;如果该网站使用的是HTTPS连接&#xff0c;那么浏览器会对其进行安全性的检查。其中一项重要的检查就是确认该网站是否拥有有效的SSL证书。然而&#xff0c;有时我们会在浏览器中看到“不安全”的警告&#xff0c;这通常是由于SSL证书…

【Go自学版】02-goroutine

利用时间片分割进程&#xff0c;致使宏观上A,B,C同时执行&#xff08;并发&#xff09; CPU利用率包含了执行和切换&#xff0c;进程/线程的数量越多&#xff0c;切换成本也会增大 最大并行数&#xff1a;GOMAXPROCS work stealing: 偷其他队列的G hand off: 当前G1阻塞&#…

中缀表达式转后缀表达式与后缀表达式计算(详解)

**中缀表达式转后缀表达式的一般步骤如下&#xff1a; 1&#xff1a;创建一个空的栈和一个空的输出列表。 2&#xff1a;从左到右扫描中缀表达式的每个字符。 3&#xff1a;如果当前字符是操作数&#xff0c;则直接将其加入到输出列表中。 4&#xff1a;如果当前字符是运算符&a…

禅道v11.6 基于linux环境下的docker容器搭建的靶场

一、环境搭建 linux环境下的 在docker环境下安装禅道CMS V11.6 docker run --name zentao_v11.6 -p 8084:80 -v /u01/zentao/www:/app/zentaopms -v /u01/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD123456 -d docker.io/yunwisdom/zentao:v11.6二、常见问题 1.删除…

软件开发的代码审查工具

在进行软件开发时&#xff0c;代码审查&#xff08;Code Review&#xff09;是一种非常重要的实践&#xff0c;它有助于发现潜在的问题、提高代码质量&#xff0c;并促使团队成员之间的知识共享。有许多工具可用于简化和优化代码审查过程。以下是一些常见的代码审查工具&#x…

力扣541.反转字符串 II

文章目录 力扣541.反转字符串 II示例代码实现总结收获 力扣541.反转字符串 II 示例 代码实现 class Solution {public String reverseStr(String s, int k) {char[] ans s.toCharArray();for(int i0;i<ans.length;i2*k){int begin i;int end Math.min(ans.length-1,begin…

【EI会议征稿】2024年粤港澳大湾区数字经济与人工智能国际学术会议(DEAI2024)

2024年粤港澳大湾区数字经济与人工智能国际学术会议(DEAI2024) 2024 Guangdong-Hong Kong-Macao Greater Bay Area International Conference on Digital Economy and Artificial Intelligence(DEAI2024) 2024年粤港澳大湾区数字经济与人工智能国际学术会议(DEAI2024)由广东科…

Emacs之dired模式重新绑定键值v(一百三十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

搞懂HashTable, HashMap, ConcurrentHashMap 的区别,看着一篇就足够了!!!

&#x1f6e9;️&#x1f6e9;️&#x1f6e9;️ 今天给大家分享的是 HashTable, HashMap, ConcurrentHashMap之间的区别&#xff0c;也是自己学习过程中的总结。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的…

为什么每个 Java 开发者都需要了解 Scala

前面我们一起回顾了第九期 Scala & Java Meetup 中最受关注的话题 —— jdk 并发编程的终极解决方案&#xff1a;虚拟线程&#xff0c;探讨了这一新特性对包括 Scala 在内的响应式编程语言的影响。 本次 Meetup 的首位分享者 Chunsen&#xff0c;在加入 Tubi 成为 Scala 开…

Linux 进程优先级

什么是进程的优先级 优先级&#xff1a;对资源的访问顺序&#xff01;注意优先级与权限的区别&#xff0c;优先级决定的是访问资源的顺序&#xff0c;这意味着无论是谁都可以访问到资源&#xff1b;但是如果你没有权限&#xff0c;你是不能访问资源的&#xff01; 这个应该比较…

XXL-JOB日志相关报错的原因

1.问题&#xff1a;msg&#xff1a;job handler [myJobHandler] not found. 原因&#xff1a;执行器中没有对应的执行器。 执行器中代码展示&#xff1a; Component Slf4j public class JobHandler {XxlJob(value "abcHandler")public void abcHandler() {log.inf…

卷积神经网络中用1*1 卷积有什么作用或者好处呢?

一、来源&#xff1a;[1312.4400] Network In Network &#xff08;如果11卷积核接在普通的卷积层后面&#xff0c;配合激活函数&#xff0c;即可实现network in network的结构&#xff09; 二、应用&#xff1a;GoogleNet中的Inception、ResNet中的残差模块 三、作用&#x…

【C++】:STL源码剖析之vector类容器的底层模拟实现

&#x1f4da;1.vector接口总览 namespace lyp {//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector(); //构造函数vector(size_t n, const …

微信小程序 - 创建 ZIP 压缩包

微信小程序 - 创建 ZIP 压缩包 场景分享代码片段导入 JSZip创建ZIP文件追加写入文件测试方法参考资料 场景 微信小程序只提供了解压ZIP的API&#xff0c;并没有提供创建ZIP的方法。 当我们想把自己处理好的保存&#xff0c;打包ZIP保存下来时就需要自己实现了。 分享代码片段…

freeswitch如何解决sip信令的NAT问题

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 公网环境复杂多变&#xff0c;客户环境更是各种稀奇古怪的问题。 fs在针对sip信令的NAT问题有针对性的参数设置。 本文讨论的范围限于fs的公网地址正常没有在NAT后面的两种常见场景。其他更多更复杂的NAT场景暂不讨论…

滴滴公布故障原因

故障原因公布&#xff1a; “底层系统软件发生故障” 业界传闻是因 “K8s升级版本错误导致”。 巧的是&#xff0c;在 10 月 17 日 “滴滴技术” 发布的《滴滴弹性云基于 K8S 的调度实践》一文中&#xff0c;也刚好有介绍滴滴从 K8s 1.12 到 1.20 跨版本升级的方案。在替换方案…