package org.jabref.model.entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jabref.model.ChainNode;
import org.jabref.model.util.OptionalUtil;
/**
* Represents a keyword in a chain of keywords.
* For example, "JabRef" in "Bibliographic manager > Awesome ones > JabRef"
*/
public class Keyword extends ChainNode<Keyword> implements Comparable<Keyword> {
public static Character DEFAULT_HIERARCHICAL_DELIMITER = '>';
private final String keyword;
public Keyword(String keyword) {
super(Keyword.class);
this.keyword = Objects.requireNonNull(keyword).trim();
}
/**
* Connects all the given keywords into one chain and returns its root,
* e.g. "A", "B", "C" is transformed into "A > B > C".
*/
public static Keyword of(String... keywords) {
if (keywords.length == 0) {
return new Keyword("");
}
Keyword root = new Keyword(keywords[0]);
for (int i = 1; i < keywords.length; i++) {
root.addAtEnd(keywords[i]);
}
return root;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Keyword other = (Keyword) o;
return Objects.equals(this.keyword, other.keyword)
// && Objects.equals(this.getParent(), other.getParent()) : we can't check the parents because then we would run in circles
&& Objects.equals(this.getChild(), other.getChild());
}
@Override
public int hashCode() {
return Objects.hash(keyword);
}
@Override
public String toString() {
return getSubchainAsString(DEFAULT_HIERARCHICAL_DELIMITER);
}
@Override
public int compareTo(Keyword o) {
return keyword.compareTo(o.keyword);
}
/**
* Adds the given keyword at the end of the chain.
* E.g., "A > B > C" + "D" -> "A > B > C > D".
*/
private void addAtEnd(String keyword) {
addAtEnd(new Keyword(keyword));
}
/**
* Returns a text representation of the subchain starting at this item.
* E.g., calling {@link #getSubchainAsString(Character)} on the node "B" in "A > B > C" returns "B > C".
*/
private String getSubchainAsString(Character hierarchicalDelimiter) {
return keyword +
getChild().map(child -> " " + hierarchicalDelimiter + " " + child.getSubchainAsString(hierarchicalDelimiter))
.orElse("");
}
/**
* Gets the keyword of this node in the chain.
*/
public String get() {
return keyword;
}
/**
* Returns a text representation of the path from the root to this item.
* E.g., calling {@link #getPathFromRootAsString(Character)} on the node "B" in "A > B > C" returns "A > B".
*/
public String getPathFromRootAsString(Character hierarchicalDelimiter) {
return getParent()
.map(parent -> parent.getPathFromRootAsString(hierarchicalDelimiter) + " " + hierarchicalDelimiter + " ")
.orElse("")
+ keyword;
}
/**
* Returns all nodes in this chain as separate keywords.
* E.g, for "A > B > C" we get {"A", "B", "C"}.
*/
public Set<Keyword> flatten() {
return Stream.concat(
Stream.of(this),
OptionalUtil.toStream(getChild()).flatMap(child -> child.flatten().stream()))
.collect(Collectors.toSet());
}
/**
* Returns all subchains starting at this node.
* E.g., for the chain "A > B > C" the subchains {"A", "A > B", "A > B > C"} are returned.
*/
public Set<String> getAllSubchainsAsString(Character hierarchicalDelimiter) {
return flatten().stream()
.map(subchain -> subchain.getPathFromRootAsString(hierarchicalDelimiter))
.collect(Collectors.toSet());
}
}