/* * Copyright 2016 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 static com.scrivenvar.definition.FindMode.*; 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). * @author White Magic Software, Ltd. */ 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. * * @param text The text to match against each leaf in the tree. * @return The leaf that has a value starting with the given text. */ public VariableTreeItem<T> findLeaf( final String text ) { return findLeaf( text, STARTS_WITH ); } /** * 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. */ public VariableTreeItem<T> findLeaf( final String text, final FindMode findMode ) { final Stack<VariableTreeItem<T>> stack = new Stack<>(); final VariableTreeItem<T> root = this; stack.push( root ); // Don't try to find keys for blank/empty variable values. boolean found = text.isBlank(); VariableTreeItem<T> node = null; while( !found && !stack.isEmpty() ) { node = stack.pop(); if( findMode == EXACT && node.valueEquals( text ) ) { found = true; } else if( findMode == CONTAINS && node.valueContains( text ) ) { found = true; } else if( findMode == STARTS_WITH && node.valueStartsWith( text ) ) { found = true; } else { for( final TreeItem<T> child : node.getChildren() ) { stack.push( (VariableTreeItem<T>) child ); } // No match found, yet. node = null; } } return node; } /** * Returns the value of the string without diacritic marks. * * @return A non-null, possibly empty string. */ private String getDiacriticlessValue() { final String value = getValue().toString(); final String normalized = Normalizer.normalize( value, NFD ); return normalized.replaceAll( "\\p{M}", "" ); } /** * 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 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 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 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() ); } }