dea插件开发-自定义语言9-Rename Refactoring

        Rename 重构操作与Find Usages的重构操作非常相似。它使用相同的规则来定位要重命名的元素,并使用相同的单词索引来查找可能引用了被重命名元素的文件。执行重命名重构时,调用方法PsiNamedElement.setName()会为重命名的元素,调用该方法PsiReference.handleElementRename()为所有对重命名元素的引用。

        这些方法基本上执行相同的操作:将 PSI 元素的底层 AST 节点替换为包含用户输入的新文本的节点。从头开始创建一个完全正确的 AST 节点非常棘手。因此,获得替换节点的最简单方法是用自定义语言创建一个虚拟文件,以便它在其解析树中包含必要的节点,构建解析树并从中提取所需的节点。

public class PropertyImpl extends PropertiesStubElementImpl<PropertyStub> implements Property, PsiLanguageInjectionHost, PsiNameIdentifierOwner {
  private static final Logger LOG = Logger.getInstance(PropertyImpl.class);

  private static final Pattern PROPERTIES_SEPARATOR = Pattern.compile("^\\s*\\n\\s*\\n\\s*$");

  public PropertyImpl(@NotNull ASTNode node) {
    super(node);
  }

  public PropertyImpl(final PropertyStub stub, final IStubElementType nodeType) {
    super(stub, nodeType);
  }

  @Override
  public String toString() {
    return "Property{ key = " + getKey() + ", value = " + getValue() + "}";
  }

  @Override
  public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
    PropertyImpl property = (PropertyImpl)PropertiesElementFactory.createProperty(getProject(), name, "xxx", null);
    ASTNode keyNode = getKeyNode();
    ASTNode newKeyNode = property.getKeyNode();
    LOG.assertTrue(newKeyNode != null);
    if (keyNode == null) {
      getNode().addChild(newKeyNode);
    }
    else {
      getNode().replaceChild(keyNode, newKeyNode);
    }
    return this;
  }

  @Override
  public void setValue(@NotNull String value) throws IncorrectOperationException {
    setValue(value, PropertyKeyValueFormat.PRESENTABLE);
  }

  @Override
  public void setValue(@NotNull String value, @NotNull PropertyKeyValueFormat format) throws IncorrectOperationException {
    ASTNode node = getValueNode();
    PropertyImpl property = (PropertyImpl)PropertiesElementFactory.createProperty(getProject(), "xxx", value, getKeyValueDelimiter(), format);
    ASTNode valueNode = property.getValueNode();
    if (node == null) {
      if (valueNode != null) {
        getNode().addChild(valueNode);
      }
    }
    else {
      if (valueNode == null) {
        getNode().removeChild(node);
      }
      else {
        getNode().replaceChild(node, valueNode);
      }
    }
  }

  @Override
  public String getName() {
    return getUnescapedKey();
  }

  @Override
  public String getKey() {
    final PropertyStub stub = getStub();
    if (stub != null) {
      return stub.getKey();
    }

    final ASTNode node = getKeyNode();
    if (node == null) {
      return null;
    }
    return node.getText();
  }

  @Nullable
  public ASTNode getKeyNode() {
    return getNode().findChildByType(PropertiesTokenTypes.KEY_CHARACTERS);
  }

  @Nullable
  public ASTNode getValueNode() {
    return getNode().findChildByType(PropertiesTokenTypes.VALUE_CHARACTERS);
  }

  @Override
  public String getValue() {
    final ASTNode node = getValueNode();
    if (node == null) {
      return "";
    }
    return node.getText();
  }

  @Override
  @Nullable
  public String getUnescapedValue() {
    return unescape(getValue());
  }

  @Override
  public @Nullable PsiElement getNameIdentifier() {
    final ASTNode node = getKeyNode();
    return node == null ? null : node.getPsi();
  }


  public static String unescape(String s) {
    if (s == null) return null;
    StringBuilder sb = new StringBuilder();
    parseCharacters(s, sb, null);
    return sb.toString();
  }

  public static boolean parseCharacters(String s, StringBuilder outChars, int @Nullable [] sourceOffsets) {
    assert sourceOffsets == null || sourceOffsets.length == s.length() + 1;
    int off = 0;
    int len = s.length();

    boolean result = true;
    final int outOffset = outChars.length();
    while (off < len) {
      char aChar = s.charAt(off++);
      if (sourceOffsets != null) {
        sourceOffsets[outChars.length() - outOffset] = off - 1;
        sourceOffsets[outChars.length() + 1 - outOffset] = off;
      }

      if (aChar == '\\') {
        aChar = s.charAt(off++);
        if (aChar == 'u') {
          // Read the xxxx
          int value = 0;
          boolean error = false;
          for (int i = 0; i < 4 && off < s.length(); i++) {
            aChar = s.charAt(off++);
            switch (aChar) {
              case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> value = (value << 4) + aChar - '0';
              case 'a', 'b', 'c', 'd', 'e', 'f' -> value = (value << 4) + 10 + aChar - 'a';
              case 'A', 'B', 'C', 'D', 'E', 'F' -> value = (value << 4) + 10 + aChar - 'A';
              default -> {
                outChars.append("\\u");
                int start = off - i - 1;
                int end = Math.min(start + 4, s.length());
                outChars.append(s, start, end);
                i = 4;
                error = true;
                off = end;
              }
            }
          }
          if (!error) {
            outChars.append((char)value);
          }
          else {
            result = false;
          }
        }
        else if (aChar == '\n') {
          // escaped linebreak: skip whitespace in the beginning of next line
          while (off < len && (s.charAt(off) == ' ' || s.charAt(off) == '\t')) {
            off++;
          }
        }
        else if (aChar == 't') {
          outChars.append('\t');
        }
        else if (aChar == 'r') {
          outChars.append('\r');
        }
        else if (aChar == 'n') {
          outChars.append('\n');
        }
        else if (aChar == 'f') {
          outChars.append('\f');
        }
        else {
          outChars.append(aChar);
        }
      }
      else {
        outChars.append(aChar);
      }
      if (sourceOffsets != null) {
        sourceOffsets[outChars.length() - outOffset] = off;
      }
    }
    return result;
  }

  @Nullable
  public static TextRange trailingSpaces(String s) {
    if (s == null) {
      return null;
    }
    int off = 0;
    int len = s.length();
    int startSpaces = -1;

    while (off < len) {
      char aChar = s.charAt(off++);
      if (aChar == '\\') {
        if (startSpaces == -1) startSpaces = off-1;
        aChar = s.charAt(off++);
        if (aChar == 'u') {
          // Read the xxxx
          int value = 0;
          boolean error = false;
          for (int i = 0; i < 4; i++) {
            aChar = off < s.length() ? s.charAt(off++) : 0;
            switch (aChar) {
              case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> value = (value << 4) + aChar - '0';
              case 'a', 'b', 'c', 'd', 'e', 'f' -> value = (value << 4) + 10 + aChar - 'a';
              case 'A', 'B', 'C', 'D', 'E', 'F' -> value = (value << 4) + 10 + aChar - 'A';
              default -> {
                int start = off - i - 1;
                int end = Math.min(start + 4, s.length());
                i = 4;
                error = true;
                off = end;
                startSpaces = -1;
              }
            }
          }
          if (!error) {
            if (Character.isWhitespace(value)) {
              if (startSpaces == -1) {
                startSpaces = off-1;
              }
            }
            else {
              startSpaces = -1;
            }
          }
        }
        else if (aChar == '\n') {
          // escaped linebreak: skip whitespace in the beginning of next line
          while (off < len && (s.charAt(off) == ' ' || s.charAt(off) == '\t')) {
            off++;
          }
        }
        else if (aChar == 't' || aChar == 'r') {
          if (startSpaces == -1) startSpaces = off;
        }
        else {
          if (aChar == 'n' || aChar == 'f') {
            if (startSpaces == -1) startSpaces = off;
          }
          else {
            if (Character.isWhitespace(aChar)) {
              if (startSpaces == -1) {
                startSpaces = off-1;
              }
            }
            else {
              startSpaces = -1;
            }
          }
        }
      }
      else {
        if (Character.isWhitespace(aChar)) {
          if (startSpaces == -1) {
            startSpaces = off-1;
          }
        }
        else {
          startSpaces = -1;
        }
      }
    }
    return startSpaces == -1 ? null : new TextRange(startSpaces, len);
  }

  @Override
  @Nullable
  public String getUnescapedKey() {
    return unescape(getKey());
  }

  @Nullable
  @Override
  protected Icon getElementIcon(@IconFlags int flags) {
    return PlatformIcons.PROPERTY_ICON;
  }

  @Override
  public void delete() throws IncorrectOperationException {
    final ASTNode parentNode = getParent().getNode();
    assert parentNode != null;

    ASTNode node = getNode();
    ASTNode prev = node.getTreePrev();
    ASTNode next = node.getTreeNext();
    parentNode.removeChild(node);
    if ((prev == null || prev.getElementType() == TokenType.WHITE_SPACE) && next != null &&
        next.getElementType() == TokenType.WHITE_SPACE) {
      parentNode.removeChild(next);
    }
  }

  @Override
  public PropertiesFile getPropertiesFile() {
    PsiFile containingFile = super.getContainingFile();
    if (!(containingFile instanceof PropertiesFile)) {
      LOG.error("Unexpected file type of: " + containingFile.getName());
    }
    return (PropertiesFile)containingFile;
  }

  /**
   * The method gets the upper edge of a {@link Property} instance which might either be
   * the property itself or the first {@link PsiComment} node that is related to the property
   *
   * @param property the property to get the upper edge for
   * @return the property itself or the first {@link PsiComment} node that is related to the property
   */
  static PsiElement getEdgeOfProperty(@NotNull final Property property) {
    PsiElement prev = property;
    for (PsiElement node = property.getPrevSibling(); node != null; node = node.getPrevSibling()) {
      if (node instanceof Property) break;
      if (node instanceof PsiWhiteSpace) {
        if (PROPERTIES_SEPARATOR.matcher(node.getText()).find()) break;
      }
      prev = node;
    }
    return prev;
  }

  @Override
  public String getDocCommentText() {
    final PsiElement edge = getEdgeOfProperty(this);
    StringBuilder text = new StringBuilder();
    for(PsiElement doc = edge; doc != this; doc = doc.getNextSibling()) {
      if (doc instanceof PsiComment) {
        text.append(doc.getText());
        text.append("\n");
      }
    }
    if (text.length() == 0) return null;
    return text.toString();
  }

  @NotNull
  @Override
  public PsiElement getPsiElement() {
    return this;
  }

  @Override
  @NotNull
  public SearchScope getUseScope() {
    // property ref can occur in any file
    return GlobalSearchScope.allScope(getProject());
  }

  @Override
  public ItemPresentation getPresentation() {
    return new ItemPresentation() {
      @Override
      public String getPresentableText() {
        return getName();
      }

      @Override
      public String getLocationString() {
        return getPropertiesFile().getName();
      }

      @Override
      public Icon getIcon(final boolean open) {
        return null;
      }
    };
  }

  @Override
  public boolean isValidHost() {
    return true;
  }

  @Override
  public PsiLanguageInjectionHost updateText(@NotNull String text) {
    return new PropertyManipulator().handleContentChange(this, text);
  }

  @NotNull
  @Override
  public LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() {
    return new PropertyImplEscaper(this);
  }

  public char getKeyValueDelimiter() {
    final PsiElement delimiter = findChildByType(PropertiesTokenTypes.KEY_VALUE_SEPARATOR);
    if (delimiter == null) {
      return ' ';
    }
    final String text = delimiter.getText();
    LOG.assertTrue(text.length() == 1);
    return text.charAt(0);
  }

  public void replaceKeyValueDelimiterWithDefault() {
    PropertyImpl property = (PropertyImpl)PropertiesElementFactory.createProperty(getProject(), "yyy", "xxx", null);
    final ASTNode newDelimiter = property.getNode().findChildByType(PropertiesTokenTypes.KEY_VALUE_SEPARATOR);
    final ASTNode propertyNode = getNode();
    final ASTNode oldDelimiter = propertyNode.findChildByType(PropertiesTokenTypes.KEY_VALUE_SEPARATOR);
    if (areDelimitersEqual(newDelimiter, oldDelimiter)) {
      return;
    }
    if (newDelimiter == null) {
      propertyNode.replaceChild(oldDelimiter, ASTFactory.whitespace(" "));
    } else {
      if (oldDelimiter == null) {
        propertyNode.addChild(newDelimiter, getValueNode());
        final ASTNode insertedDelimiter = propertyNode.findChildByType(PropertiesTokenTypes.KEY_VALUE_SEPARATOR);
        LOG.assertTrue(insertedDelimiter != null);
        ASTNode currentPrev = insertedDelimiter.getTreePrev();
        final List<ASTNode> toDelete = new ArrayList<>();
        while (currentPrev != null && currentPrev.getElementType() == PropertiesTokenTypes.WHITE_SPACE) {
          toDelete.add(currentPrev);
          currentPrev = currentPrev.getTreePrev();
        }
        for (ASTNode node : toDelete) {
          propertyNode.removeChild(node);
        }
      } else {
        propertyNode.replaceChild(oldDelimiter, newDelimiter);
      }
    }
  }

  private static boolean areDelimitersEqual(@Nullable ASTNode node1, @Nullable ASTNode node2) {
    if (node1 == null && node2 == null) return true;
    if (node1 == null || node2 == null) return false;
    final String text1 = node1.getText();
    final String text2 = node2.getText();
    return text1.equals(text2);
  }
}


        如果重命名的引用扩展了PsiReferenceBase,则调用ElementManipulator.handleContentChange()来执行重命名,负责处理内容更改并计算元素内引用的文本范围。要禁用特定元素的重命名,请实现com.intellij.openapi.util.Condition<T>PsiElement 类型T并将其注册到com.intellij.vetoRenameCondition扩展点。

一、名称验证

        NamesValidatorRename允许插件根据自定义语言规则检查用户在对话框中输入的名称是否是有效标识符(而不是关键字)。如果插件未提供此接口的实现,则使用用于验证标识符的 Java 规则。的实现NamesValidator在扩展点中注册com.intellij.lang.namesValidator。

public class PropertiesNamesValidator implements NamesValidator {
  @Override
  public boolean isKeyword(@NotNull final String name, final Project project) {
    return false;
  }

  @Override
  public boolean isIdentifier(@NotNull final String name, final Project project) {
    return true;
  }
}

        另一种检查方式是RenameInputValidator,不像NamesValidator它允许您更灵活地根据方法中定义的规则检查输入的名称是否正确isInputValid()。要确定此验证器将应用于哪些元素,请覆盖getPattern()返回要验证的元素模式的方法。比如以下示例:

public class YAMLAnchorRenameInputValidator implements RenameInputValidator {
  @NotNull
  @Override
  public ElementPattern<? extends PsiElement> getPattern() {
    return psiElement(YAMLAnchor.class);
  }

  @Override
  public boolean isInputValid(@NotNull String newName, @NotNull PsiElement element, @NotNull ProcessingContext context) {
    return newName.matches("[^,\\[\\]{}\\n\\t ]+");
  }
}

        RenameInputValidator可以扩展为RenameInputValidatorEx覆盖默认错误消息。getErrorMessage()如果名称无效或其他情况,该方法应返回自定义错误消息null。请注意,getErrorMessage()仅当所有人都RenameInputValidator接受新名称isInputValid()并且该名称是元素语言的有效标识符时才有效,这种方式需要实现com.intellij.renameInputValidator扩展点。

public final class YamlKeyValueRenameInputValidator implements RenameInputValidatorEx {
  private static final String IDENTIFIER_START_PATTERN = "(([^\\n\\t\\r \\-?:,\\[\\]{}#&*!|>'\"%@`])" +
                                                         "|([?:-][^\\n\\t\\r ])" +
                                                         ")";

  private static final String IDENTIFIER_END_PATTERN = "(([^\\n\\t\\r ]#)" +
                                                       "|([^\\n\\t\\r :#])" +
                                                       "|(:[^\\n\\t\\r ])" +
                                                       ")";

  // Taken from yaml.flex, NS_PLAIN_ONE_LINE_block. This may not be entirely correct, but it is less restrictive than the default names
  // validator
  public static final Pattern IDENTIFIER_PATTERN = Pattern.compile(
    "(" + IDENTIFIER_START_PATTERN + "([ \t]*" + IDENTIFIER_END_PATTERN + ")*)|" +
    "('[^\\n']*')|(\"[^\\n\"]*\")"
  );

  @Nullable
  @Override
  public String getErrorMessage(@NotNull final String newName, @NotNull final Project project) {
    return IDENTIFIER_PATTERN.matcher(newName).matches() ? null : YAMLBundle.message("rename.invalid.name", newName);
  }

  @NotNull
  @Override
  public ElementPattern<? extends PsiElement> getPattern() {
    return PlatformPatterns.psiElement(YAMLKeyValue.class);
  }

  @Override
  public boolean isInputValid(@NotNull final String newName, @NotNull final PsiElement element, @NotNull final ProcessingContext context) {
    return true;
  }
}

二、自定义重命名UI和工作流程

        可以在多个级别进一步自定义重命名重构处理。提供接口的自定义实现RenameHandler允许您完全替换 rename 重构的 UI 和工作流,并且还支持重命名根本不是PsiElement的元素。示例:用于在Properties 语言插件RenameHandler中重命名资源包

public class ResourceBundleFromEditorRenameHandler implements RenameHandler {

  @Override
  public boolean isAvailableOnDataContext(@NotNull DataContext dataContext) {
    final Project project = CommonDataKeys.PROJECT.getData(dataContext);
    if (project == null) {
      return false;
    }
    final ResourceBundle bundle = ResourceBundleUtil.getResourceBundleFromDataContext(dataContext);
    if (bundle == null) {
      return false;
    }
    final FileEditor fileEditor = PlatformCoreDataKeys.FILE_EDITOR.getData(dataContext);
    if (!(fileEditor instanceof ResourceBundleEditor)) {
      return false;
    }
    final VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
    return virtualFile instanceof ResourceBundleAsVirtualFile;
  }

  @Override
  public void invoke(final @NotNull Project project, Editor editor, final PsiFile file, DataContext dataContext) {
    final ResourceBundleEditor resourceBundleEditor = (ResourceBundleEditor)PlatformCoreDataKeys.FILE_EDITOR.getData(dataContext);
    assert resourceBundleEditor != null;
    final Object selectedElement = resourceBundleEditor.getSelectedElementIfOnlyOne();
    if (selectedElement != null) {
      CommandProcessor.getInstance().runUndoTransparentAction(() -> {
        if (selectedElement instanceof PropertiesPrefixGroup group) {
          ResourceBundleRenameUtil.renameResourceBundleKeySection(getPsiElementsFromGroup(group),
                                                                  group.getPresentableName(),
                                                                  group.getPrefix().length() - group.getPresentableName().length());
        } else if (selectedElement instanceof PropertyStructureViewElement) {
          final PsiElement psiElement = ((PropertyStructureViewElement)selectedElement).getPsiElement();
          ResourceBundleRenameUtil.renameResourceBundleKey(psiElement, project);
        } else if (selectedElement instanceof ResourceBundleFileStructureViewElement) {
          ResourceBundleRenameUtil.renameResourceBundleBaseName(((ResourceBundleFileStructureViewElement)selectedElement).getValue(), project);
        } else {
          throw new IllegalStateException("unsupported type: " + selectedElement.getClass());
        }
      });
    }
  }

  @Override
  public void invoke(@NotNull Project project, PsiElement @NotNull [] elements, DataContext dataContext) {
    invoke(project, null, null, dataContext);
  }

  private static List<PsiElement> getPsiElementsFromGroup(final PropertiesPrefixGroup propertiesPrefixGroup) {
    return ContainerUtil.mapNotNull(propertiesPrefixGroup.getChildren(), treeElement -> {
      if (treeElement instanceof PropertyStructureViewElement) {
        return ((PropertyStructureViewElement)treeElement).getPsiElement();
      }
      return null;
    });

如果您对标准 UI 没问题但需要扩展重命名的默认逻辑,您可以提供接口的实现RenamePsiElementProcessor,以实现以下功能:

1、重命名与调用操作的元素不同的元素(例如,超级方法)

2、一次重命名多个元素(如果它们的名称根据您的语言逻辑链接)

3、检查名称冲突(现有名称等)

4、自定义搜索代码参考或文本参考的方式

示例:用于重命名Properties 插件语言RenamePsiElementProcessor中的属性

public abstract class RenamePsiElementProcessor extends RenamePsiElementProcessorBase {
  @NotNull
  public RenameDialog createRenameDialog(@NotNull Project project,
                                         @NotNull PsiElement element,
                                         @Nullable PsiElement nameSuggestionContext,
                                         @Nullable Editor editor) {
    return new RenameDialog(project, element, nameSuggestionContext, editor);
  }

  @Override
  public RenameRefactoringDialog createDialog(@NotNull Project project,
                                                             @NotNull PsiElement element,
                                                             @Nullable PsiElement nameSuggestionContext,
                                                             @Nullable Editor editor) {
    return this.createRenameDialog(project, element, nameSuggestionContext, editor);
  }

  @NotNull
  public static RenamePsiElementProcessor forElement(@NotNull PsiElement element) {
    for (RenamePsiElementProcessorBase processor : EP_NAME.getExtensionList()) {
      if (processor.canProcessElement(element)) {
        return (RenamePsiElementProcessor)processor;
      }
    }
    return DEFAULT;
  }

  @NotNull
  public static List<RenamePsiElementProcessor> allForElement(@NotNull PsiElement element) {
    final List<RenamePsiElementProcessor> result = new ArrayList<>();
    for (RenamePsiElementProcessorBase processor : EP_NAME.getExtensions()) {
      if (processor.canProcessElement(element)) {
        result.add((RenamePsiElementProcessor)processor);
      }
    }
    return result;
  }

  private static class MyRenamePsiElementProcessor extends RenamePsiElementProcessor implements DefaultRenamePsiElementProcessor {
    @Override
    public boolean canProcessElement(@NotNull final PsiElement element) {
      return true;
    }
  }

  public static final RenamePsiElementProcessor DEFAULT = new MyRenamePsiElementProcessor();
}

​三、示例实现

10. Reference Contributor | IntelliJ Platform Plugin SDK

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

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

相关文章

SOMEIP_ETS_139: SD_Options_Array_too_short

测试目的&#xff1a; 验证DUT能够拒绝一个选项数组长度短于所需的SubscribeEventgroup消息&#xff0c;并以SubscribeEventgroupNAck作为响应。 描述 本测试用例旨在确保DUT遵循SOME/IP协议&#xff0c;当接收到一个选项数组长度不足以包含所有必需选项的SubscribeEventgro…

【C++篇】启航——初识C++(上篇)

目录 引言 一、C的起源和发展史 1.起源 2.C版本更新 二、C在⼯作领域中的应⽤ 三、C入门建议 1.参考文档 2.推荐书籍 四、C的第一个程序 1.C语言写法 2.C写法 五、命名空间 1.为什么要有命名空间 2.定义命名空间 3.主要特点 4.使用示例 六、C输⼊&输出 …

【muduo源码分析】「阻塞」「非阻塞」「同步」「异步」

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 引言何为「muduo库」安装muduo库阻塞、非阻塞、同步、异步数据准备数据准备 引言 从本篇博客开始&#xff0c;我会陆续发表muduo库源码分析的相关文章。感谢大家的持续关注&#xff01;&#xff01;…

Vue|插件

在 Vue.js 中&#xff0c;插件是用来扩展 Vue 功能的一种方式&#xff0c;能够帮助开发者扩展和复用功能。通过合理使用插件&#xff0c;可以提高代码的组织性和可维护性 目录 如何使用插件?插件的定义创建及使用插件插件的参数插件的扩展 总结 如何使用插件? 插件的定义 插…

2-107 基于matlab的hsv空间双边滤波去雾图像增强算法

基于matlab的hsv空间双边滤波去雾图像增强算法&#xff0c;原始图像经过光照增强后&#xff0c;将RGB转成hsv&#xff0c;进行图像增强处理&#xff0c;使图像更加清晰。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&#xff1a; 2-107 基于matlab的hsv空间双边滤…

论文阅读 | 可证安全隐写(网络空间安全科学学报 2023)

可证安全隐写&#xff1a;理论、应用与展望 一、什么是可证安全隐写&#xff1f; 对于经验安全的隐写算法&#xff0c;即使其算法设计得相当周密&#xff0c;隐写分析者&#xff08;攻击者&#xff09;在观察了足够数量的载密&#xff08;含有隐写信息的数据&#xff09;和载体…

推荐4款2024年热门的PDF转ppt工具

有时候&#xff0c;我们为了方便&#xff0c;需要将PDF里面的内容直接转换的PPT的格式&#xff0c;既方便自己演示和讲解&#xff0c;也让我们可以更加灵活的进行文件的编辑和修改。如果大家不知道要如何进行操作的话&#xff0c;我可以为大家推荐几个比窘方便实用的PDF转换工具…

html TAB、table生成

1. 代码 <!DOCTYPE html> <head> <meta charset"UTF-8"> <title>Dynamic Tabs with Table Data</title> <style> /* 简单的样式 */ .tab-content { display: none; border: 10px solid #ccc; padding: 30px; mar…

赛氪作媒体支持单位受邀参加首届科普翻译与跨学科专业学术研讨会

2024年9月22日&#xff0c;正值全国科普日之际&#xff0c;首届科普翻译与跨学科专业学术研讨会在上海健康与营养研究所信息中心励志厅成功举行并圆满结束。此次研讨会汇聚了来自全国各地的近60名专家学者、学界及企业界代表&#xff0c;共同探讨科普翻译与跨学科专业的发展。作…

BaseCTF2024 web

Web [Week1] HTTP 是什么呀 GET: ?basectf%77%65%31%63%25%30%30%6d%65POST: BaseflgX-Forwarded-For:127.0.0.1Referer: BaseCookie: c00k13i cant eat itUser-Agent: Base有Location跳转, 抓包得到flag: QmFzZUNURntkZGUzZjA0Yy1hMDg5LTQwNGMtOTFjNi01ODZjMzAxMzM3Y2J9Cg…

Springboot jPA+thymeleaf实现增删改查

项目结构 pom文件 配置相关依赖&#xff1a; 2.thymeleaf有点类似于jstlel th:href"{url}表示这是一个链接 th:each"user : ${users}"相当于foreach&#xff0c;对user进行循环遍历 th:if进行if条件判断 {变量} 与 ${变量}的区别: 4.配置好application.ym…

基于SpringBoot+Vue+MySQL的体育商城系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网的飞速发展&#xff0c;电子商务已成为人们日常生活中不可或缺的一部分。体育用品市场作为其中的一个重要分支&#xff0c;也逐渐向线上转移。基于SpringBootVueMySQL的体育商城系统应运而生&#xff0c;旨在通过构建…

E34.【C语言】位段练习题

1.题目 分析下列代码中位段在内存中的排布(已知测试平台为VS2022) struct S {int a : 2;int b : 5;int c : 10;int d : 30; };int main() {struct S s { 0 };return 0; } 有关位段的知识点见64.【C语言】再议结构体(下)文 2.提示 VS满足:由低地址向高地址存储,按浪费空间…

Halo 专业版,含推荐码

立即体验 准备好体验 Halo 了吗&#xff1f;你可以查阅我们完整的部署文档&#xff0c;或者在线使用 Demo 进行体验。 部署指南&#xff1a;www.halo.run Halo 专业版在购买界面输入优惠码&#xff1a; lqMawh8t 使用优惠码优惠码购买专业版&#xff0c;单件 7 折&#xff…

【在Linux世界中追寻伟大的One Piece】进程间通信

目录 1 -> 进程间通信介绍 1.1 -> 进程间通信目的 1.2 -> 进程间通信发展 1.3 -> 进程间通信分类 1.3.1 -> 管道 1.3.2 -> System V IPC 1.3.3 -> POSIX IPC 2 -> 管道 2.1 -> 什么是管道 2.2 -> 匿名管道 2.3 -> 实例代码 2.4 -…

Java | Leetcode Java题解之第436题寻找右区间

题目&#xff1a; 题解&#xff1a; class Solution {public int[] findRightInterval(int[][] intervals) {int n intervals.length;int[][] startIntervals new int[n][2];int[][] endIntervals new int[n][2];for (int i 0; i < n; i) {startIntervals[i][0] inter…

回归预测 | Matlab基于SO-SVR蛇群算法优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于SO-SVR蛇群算法优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SO-SVR蛇群算法优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab基于SO-SVR蛇群算法优化支持向量机的数据多…

828华为云征文|华为云Flexus云服务器X实例——部署EduSoho网校系统、二次开发对接华为云视频点播实现CDN加速播放

EduSoho 是一款功能强大的网校系统&#xff0c;能够帮助教育机构快速搭建在线学习平台。本文将详细介绍如何在华为云服务器上安装和部署 EduSoho 网校系统&#xff0c;以及二次开发对接华为云视频点播VOD来实现CDN加速播放。 edusoho本地存储的视频播放存在诸多弊端。一方面&a…

建立分支提交代码

git分支 git branch 产看当前分支 git branch -a 查看所有分支 git checkout 分支名 切换分支 git checkout -b 分支名 建立分支&#xff08;仅仅是在本地建立了&#xff0c;并没有关联线上&#xff09; git push --set-upstream origin 分支名 把本地分支推到先线上 gti add …

Spring Boot 整合MyBatis-Plus 实现多层次树结构的异步加载功能

文章目录 1&#xff0c;前言2&#xff0c;什么是多层次树结构&#xff1f;3&#xff0c;异步加载的意义4&#xff0c;技术选型与实现思路5&#xff0c;具体案例5.1&#xff0c;项目结构5.2&#xff0c;项目配置&#xff08;pom.xml&#xff09;5.3&#xff0c;配置文件&#xf…