/* * Copyright 2020 White Magic Software, Ltd. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.scrivenvar.definition; import javafx.scene.control.TreeItem; import java.text.Normalizer; import java.util.Stack; import java.util.function.BiFunction; import static java.text.Normalizer.Form.NFD; /** * Provides behaviour afforded to variable names and their corresponding value. * * @param <T> The type of TreeItem (usually String). */ public class VariableTreeItem<T> extends TreeItem<T> { /** * Constructs a new item with a default value. * * @param value Passed up to superclass. */ public VariableTreeItem( final T value ) { super( value ); } /** * Finds a leaf starting at the current node with text that matches the given * value. Search is performed case-sensitively. * * @param text The text to match against each leaf in the tree. * @return The leaf that has a value exactly matching the given text. */ public VariableTreeItem<T> findLeafExact( final String text ) { return findLeaf( text, VariableTreeItem::valueEquals ); } /** * Finds a leaf starting at the current node with text that matches the given * value. Search is performed case-sensitively. * * @param text The text to match against each leaf in the tree. * @return The leaf that has a value that contains the given text. */ public VariableTreeItem<T> findLeafContains( final String text ) { return findLeaf( text, VariableTreeItem::valueContains ); } /** * Finds a leaf starting at the current node with text that matches the given * value. Search is performed case-insensitively. * * @param text The text to match against each leaf in the tree. * @return The leaf that has a value that contains the given text. */ public VariableTreeItem<T> findLeafContainsNoCase( final String text ) { return findLeaf( text, VariableTreeItem::valueContainsNoCase ); } /** * Finds a leaf starting at the current node with text that matches the given * value. Search is performed case-sensitively. * * @param text The text to match against each leaf in the tree. * @return The leaf that has a value that starts with the given text. */ public VariableTreeItem<T> findLeafStartsWith( final String text ) { return findLeaf( text, VariableTreeItem::valueStartsWith ); } /** * Finds a leaf starting at the current node with text that matches the given * value. * * @param text The text to match against each leaf in the tree. * @param findMode What algorithm is used to match the given text. * @return The leaf that has a value starting with the given text, or {@code * null} if there was no match found. */ public VariableTreeItem<T> findLeaf( final String text, final BiFunction<VariableTreeItem<T>, String, Boolean> findMode ) { final Stack<VariableTreeItem<T>> stack = new Stack<>(); stack.push( this ); // Don't hunt for blank (empty) keys. boolean found = text.isBlank(); while( !found && !stack.isEmpty() ) { final VariableTreeItem<T> node = stack.pop(); for( final TreeItem<T> child : node.getChildren() ) { final VariableTreeItem<T> result = (VariableTreeItem<T>) child; if( result.isLeaf() ) { if( found = findMode.apply( result, text ) ) { return result; } } else { stack.push( result ); } } } return null; } /** * Returns the value of the string without diacritic marks. * * @return A non-null, possibly empty string. */ private String getDiacriticlessValue() { return Normalizer.normalize( getValue().toString(), NFD ) .replaceAll( "\\p{M}", "" ); } /** * Returns true if this node is a leaf and its value equals the given text. * * @param s The text to compare against the node value. * @return true Node is a leaf and its value equals the given value. */ private boolean valueEquals( final String s ) { return isLeaf() && getValue().equals( s ); } /** * Returns true if this node is a leaf and its value contains the given text. * * @param s The text to compare against the node value. * @return true Node is a leaf and its value contains the given value. */ private boolean valueContains( final String s ) { return isLeaf() && getDiacriticlessValue().contains( s ); } /** * Returns true if this node is a leaf and its value contains the given text. * * @param s The text to compare against the node value. * @return true Node is a leaf and its value contains the given value. */ private boolean valueContainsNoCase( final String s ) { return isLeaf() && getDiacriticlessValue() .toLowerCase() .contains( s.toLowerCase() ); } /** * Returns true if this node is a leaf and its value starts with the given * text. * * @param s The text to compare against the node value. * @return true Node is a leaf and its value starts with the given value. */ private boolean valueStartsWith( final String s ) { return isLeaf() && getDiacriticlessValue().startsWith( s ); } /** * Returns the path for this node, with nodes made distinct using the * separator character. This uses two loops: one for pushing nodes onto a * stack and one for popping them off to create the path in desired order. * * @return A non-null string, possibly empty. */ public String toPath() { return TreeItemAdapter.toPath( getParent() ); } }