
dev.dejvokep.boostedyaml.block.Block Maven / Gradle / Ivy
/*
* Copyright 2024 https://dejvokep.dev/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.dejvokep.boostedyaml.block;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import dev.dejvokep.boostedyaml.block.implementation.TerminatedBlock;
import dev.dejvokep.boostedyaml.utils.format.NodeRole;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.snakeyaml.engine.v2.comments.CommentLine;
import org.snakeyaml.engine.v2.comments.CommentType;
import org.snakeyaml.engine.v2.nodes.MappingNode;
import org.snakeyaml.engine.v2.nodes.Node;
import org.snakeyaml.engine.v2.nodes.NodeTuple;
import org.snakeyaml.engine.v2.nodes.SequenceNode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Represents one YAML block, while storing its value and comments.
*
* @param type of the value stored
*/
@SuppressWarnings({"unused", "UnusedReturnValue"})
public abstract class Block {
//Comments
@Nullable
List beforeKeyComments = null, inlineKeyComments = null, afterKeyComments = null, beforeValueComments = null, inlineValueComments = null, afterValueComments = null;
//Value
private T value;
//If to ignore
private boolean ignored;
/**
* Creates a block using the given parameters; while storing references to comments from the given nodes.
*
* @param keyNode node which represents the key to the block
* @param valueNode node which represents the value
* @param value the value to store
*/
public Block(@Nullable Node keyNode, @Nullable Node valueNode, @Nullable T value) {
this.value = value;
init(keyNode, valueNode);
}
/**
* Creates a block with the given value, but no comments.
*
* This constructor is only used by extending classes, where the comments (respective nodes) are unknown at the
* time of initialization. In such a scenario, it is needed to call {@link #init(Node, Node)} afterwards.
*
* @param value the value to store
*/
public Block(@Nullable T value) {
this(null, null, value);
}
/**
* Creates a block with the same comments as the provided previous block, with the given value. If given block is
* null
, creates a block with no comments.
*
* @param previous the previous block to reference comments from
* @param value the value to store
*/
public Block(@Nullable Block> previous, @Nullable T value) {
//Set
this.value = value;
//If null
if (previous == null)
return;
//Set
this.beforeKeyComments = previous.beforeKeyComments;
this.inlineKeyComments = previous.inlineKeyComments;
this.afterKeyComments = previous.afterKeyComments;
this.beforeValueComments = previous.beforeValueComments;
this.inlineValueComments = previous.inlineValueComments;
this.afterValueComments = previous.afterValueComments;
}
/**
* Stores comments from the given nodes. Only method which is able to mutate an instance of this class.
*
* This method can also be referred to as the secondary constructor.
*
* @param key node which represents the key to the block
* @param value node which represents the value
*/
@SuppressWarnings("ConstantConditions")
protected void init(@Nullable Node key, @Nullable Node value) {
//If not null
if (key != null) {
// Set
beforeKeyComments = key.getBlockComments() == null ? new ArrayList<>(0) : key.getBlockComments();
inlineKeyComments = key.getInLineComments();
afterKeyComments = key.getEndComments();
// Collect
collectComments(key, beforeKeyComments, true);
}
//If not null
if (value != null) {
// Set
beforeValueComments = value.getBlockComments() == null ? new ArrayList<>(0) : value.getBlockComments();
inlineValueComments = value.getInLineComments();
afterValueComments = value.getEndComments();
// Collect
collectComments(value, beforeValueComments, true);
}
}
/**
* Collects all comments from this (only if not the initial node) and all sub-nodes and assigns them to the provided
* destination list. Inline comments are automatically converted to block comments.
*
* @param node the node to collect from
* @param destination the destination list
* @param initial if this node is the initial one in the recursive call stack
*/
private void collectComments(@NotNull Node node, @NotNull List destination, boolean initial) {
// Add
if (!initial) {
if (node.getBlockComments() != null)
destination.addAll(toBlockComments(node.getBlockComments()));
if (node.getInLineComments() != null)
destination.addAll(toBlockComments(node.getInLineComments()));
if (node.getEndComments() != null)
destination.addAll(toBlockComments(node.getEndComments()));
}
// If is a sequence node
if (node instanceof SequenceNode) {
// The node
SequenceNode sequenceNode = (SequenceNode) node;
// Iterate
for (Node sub : sequenceNode.getValue())
// Collect
collectComments(sub, destination, false);
} else if (!initial && node instanceof MappingNode) {
// The node
MappingNode mappingNode = (MappingNode) node;
// Iterate
for (NodeTuple sub : mappingNode.getValue()) {
// Collect
collectComments(sub.getKeyNode(), destination, false);
collectComments(sub.getValueNode(), destination, false);
}
}
}
/**
* Converts all comments of type {@link CommentType#IN_LINE} within this list to their {@link CommentType#BLOCK}
* equivalent.
*
* This method mutates the given list and returns it.
*
* @param commentLines lines to convert
* @return the given list
*/
private List toBlockComments(@NotNull List commentLines) {
// Index
int i = -1;
// Convert
for (CommentLine commentLine : commentLines)
commentLines.set(++i, commentLine.getCommentType() != CommentType.IN_LINE ? commentLine : new CommentLine(commentLine.getStartMark(), commentLine.getEndMark(), commentLine.getValue(), CommentType.BLOCK));
// Return
return commentLines;
}
/**
* Sets a new value.
*
* @param value the new value
*/
public void setValue(T value) {
this.value = value;
}
/**
* Returns comments (at {@link NodeRole#KEY} node at {@link Comments.Position#BEFORE} position).
*
* This method will return null
or an empty {@link List}, indicating there are no comments.
*
* @return the comments
* @see Comments#get(Block, NodeRole, Comments.Position)
*/
@Nullable
public List getComments() {
// Comments
List comments = Comments.get(this, NodeRole.KEY, Comments.Position.BEFORE);
// If null
if (comments == null)
return null;
// Map
return comments.stream().map(CommentLine::getValue).collect(Collectors.toList());
}
/**
* Sets the given comments (at {@link NodeRole#KEY} node at {@link Comments.Position#BEFORE} position).
*
* To remove comments, use {@link #removeComments()} instead. Alternatively, pass either
* null
or an empty {@link List} as the parameter.
*
* @param comments the comments to set
* @see Comments#set(Block, NodeRole, Comments.Position, List)
*/
public void setComments(@Nullable List comments) {
Comments.set(this, NodeRole.KEY, Comments.Position.BEFORE, comments == null ? null : comments.stream().map(comment -> Comments.create(comment, Comments.Position.BEFORE)).collect(Collectors.toList()));
}
/**
* Removes all comments (at {@link NodeRole#KEY} node at {@link Comments.Position#BEFORE} position).
*
* @see Comments#remove(Block, NodeRole, Comments.Position)
*/
public void removeComments() {
Comments.remove(this, NodeRole.KEY, Comments.Position.BEFORE);
}
/**
* Adds the given comments to already existing comments (at {@link NodeRole#KEY} node at
* {@link Comments.Position#BEFORE} position).
*
* @param comments the comments to add
* @see Comments#add(Block, NodeRole, Comments.Position, List)
*/
public void addComments(@NotNull List comments) {
Comments.add(this, NodeRole.KEY, Comments.Position.BEFORE, comments.stream().map(comment -> Comments.create(comment, Comments.Position.BEFORE)).collect(Collectors.toList()));
}
/**
* Adds the given comment to already existing comments (at {@link NodeRole#KEY} node at
* {@link Comments.Position#BEFORE} position).
*
* @param comment the comment to add
* @see Comments#add(Block, NodeRole, Comments.Position, CommentLine)
*/
public void addComment(@NotNull String comment) {
Comments.add(this, NodeRole.KEY, Comments.Position.BEFORE, Comments.create(comment, Comments.Position.BEFORE));
}
/**
* Sets if to ignore this block. Used only internally while updating.
*
* @param ignored if to ignore this block
*/
public void setIgnored(boolean ignored) {
this.ignored = ignored;
}
/**
* Returns if this block is ignored. Used only internally while updating.
*
* @return if this block is ignored
*/
public boolean isIgnored() {
return ignored;
}
/**
* Returns if this block represents a {@link Section section}.
*
* @return if this block represents a {@link Section section}
*/
public abstract boolean isSection();
/**
* Returns the stored value.
*
* For {@link Section sections}, this is a {@link java.util.Map}; for {@link TerminatedBlock terminated blocks} an
* {@link Object}.
*
* @return the stored value
*/
public T getStoredValue() {
return value;
}
}