org.thymeleaf.engine.AttributeNames Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of thymeleaf Show documentation
Show all versions of thymeleaf Show documentation
Modern server-side Java template engine for both web and standalone environments
/*
* =============================================================================
*
* Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org)
*
* 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 org.thymeleaf.engine;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.util.TextUtils;
/**
*
* @author Daniel Fernández
* @since 3.0.0
*
*/
public class AttributeNames {
// We need a different repository for each type of name
private static final AttributeNamesRepository htmlAttributeNamesRepository = new AttributeNamesRepository(TemplateMode.HTML);
private static final AttributeNamesRepository xmlAttributeNamesRepository = new AttributeNamesRepository(TemplateMode.XML);
private static final AttributeNamesRepository textAttributeNamesRepository = new AttributeNamesRepository(TemplateMode.TEXT);
private static TextAttributeName buildTextAttributeName(final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (attributeNameBuffer == null || attributeNameLen == 0) {
throw new IllegalArgumentException("Attribute name buffer cannot be null or empty");
}
if (attributeNameOffset < 0 || attributeNameLen < 0) {
throw new IllegalArgumentException("Attribute name offset and len must be equal or greater than zero");
}
char c;
int i = attributeNameOffset;
int n = attributeNameLen;
while (n-- != 0) {
c = attributeNameBuffer[i++];
if (c != ':') {
continue;
}
if (c == ':') {
if (i == attributeNameOffset + 1){
// ':' was the first char, no prefix there
return TextAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
return TextAttributeName.forName(
new String(attributeNameBuffer, attributeNameOffset, (i - (attributeNameOffset + 1))),
new String(attributeNameBuffer, i, (attributeNameOffset + attributeNameLen) - i));
}
}
return TextAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
private static XMLAttributeName buildXMLAttributeName(final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (attributeNameBuffer == null || attributeNameLen == 0) {
throw new IllegalArgumentException("Attribute name buffer cannot be null or empty");
}
if (attributeNameOffset < 0 || attributeNameLen < 0) {
throw new IllegalArgumentException("Attribute name offset and len must be equal or greater than zero");
}
char c;
int i = attributeNameOffset;
int n = attributeNameLen;
while (n-- != 0) {
c = attributeNameBuffer[i++];
if (c != ':') {
continue;
}
if (c == ':') {
if (i == attributeNameOffset + 1){
// ':' was the first char, no prefix there
return XMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
return XMLAttributeName.forName(
new String(attributeNameBuffer, attributeNameOffset, (i - (attributeNameOffset + 1))),
new String(attributeNameBuffer, i, (attributeNameOffset + attributeNameLen) - i));
}
}
return XMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
private static HTMLAttributeName buildHTMLAttributeName(final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (attributeNameBuffer == null || attributeNameLen == 0) {
throw new IllegalArgumentException("Attribute name buffer cannot be null or empty");
}
if (attributeNameOffset < 0 || attributeNameLen < 0) {
throw new IllegalArgumentException("Attribute name offset and len must be equal or greater than zero");
}
char c;
int i = attributeNameOffset;
int n = attributeNameLen;
boolean inData = false;
while (n-- != 0) {
c = attributeNameBuffer[i++];
if (c != ':' && c != '-') {
continue;
}
if (!inData && c == ':') {
if (i == attributeNameOffset + 1){
// ':' was the first char, no prefix there
return HTMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
if (TextUtils.equals(TemplateMode.HTML.isCaseSensitive(), "xml:", 0, 4, attributeNameBuffer, attributeNameOffset, (i - attributeNameOffset)) ||
TextUtils.equals(TemplateMode.HTML.isCaseSensitive(), "xmlns:", 0, 6, attributeNameBuffer, attributeNameOffset, (i - attributeNameOffset))) {
// 'xml' and 'xmlns' are not a valid dialect prefix in HTML mode
return HTMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
return HTMLAttributeName.forName(
new String(attributeNameBuffer, attributeNameOffset, (i - (attributeNameOffset + 1))),
new String(attributeNameBuffer, i, (attributeNameOffset + attributeNameLen) - i));
}
if (!inData && c == '-') {
if (i == attributeNameOffset + 5 && TextUtils.equals(TemplateMode.HTML.isCaseSensitive(), "data", 0, 4, attributeNameBuffer, attributeNameOffset, (i - (attributeNameOffset + 1)))) {
inData = true;
continue;
} else {
// this is just a normal, non-thymeleaf 'data-*' attribute
return HTMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
}
if (inData && c == '-') {
if (i == attributeNameOffset + 6) {
// '-' was the first char after 'data-', no prefix there
return HTMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
return HTMLAttributeName.forName(
new String(attributeNameBuffer, attributeNameOffset + 5, (i - (attributeNameOffset + 6))),
new String(attributeNameBuffer, i, (attributeNameOffset + attributeNameLen) - i));
}
}
return HTMLAttributeName.forName(null, new String(attributeNameBuffer, attributeNameOffset, attributeNameLen));
}
private static TextAttributeName buildTextAttributeName(final String attributeName) {
if (attributeName == null || attributeName.length() == 0) {
throw new IllegalArgumentException("Attribute name cannot be null or empty");
}
char c;
int i = 0;
int n = attributeName.length();
while (n-- != 0) {
c = attributeName.charAt(i++);
if (c != ':') {
continue;
}
if (c == ':') {
if (i == 1){
// ':' was the first char, no prefix there
return TextAttributeName.forName(null, attributeName);
}
return TextAttributeName.forName(
attributeName.substring(0, i - 1),
attributeName.substring(i, attributeName.length()));
}
}
return TextAttributeName.forName(null, attributeName);
}
private static XMLAttributeName buildXMLAttributeName(final String attributeName) {
if (attributeName == null || attributeName.length() == 0) {
throw new IllegalArgumentException("Attribute name cannot be null or empty");
}
char c;
int i = 0;
int n = attributeName.length();
while (n-- != 0) {
c = attributeName.charAt(i++);
if (c != ':') {
continue;
}
if (c == ':') {
if (i == 1){
// ':' was the first char, no prefix there
return XMLAttributeName.forName(null, attributeName);
}
return XMLAttributeName.forName(
attributeName.substring(0, i - 1),
attributeName.substring(i, attributeName.length()));
}
}
return XMLAttributeName.forName(null, attributeName);
}
private static HTMLAttributeName buildHTMLAttributeName(final String attributeName) {
if (attributeName == null || attributeName.length() == 0) {
throw new IllegalArgumentException("Attribute name cannot be null or empty");
}
char c;
int i = 0;
int n = attributeName.length();
boolean inData = false;
while (n-- != 0) {
c = attributeName.charAt(i++);
if (c != ':' && c != '-') {
continue;
}
if (!inData && c == ':') {
if (i == 1){
// ':' was the first char, no prefix there
return HTMLAttributeName.forName(null, attributeName);
}
if (TextUtils.equals(TemplateMode.HTML.isCaseSensitive(), "xml:", 0, 4, attributeName, 0, i) ||
TextUtils.equals(TemplateMode.HTML.isCaseSensitive(), "xmlns:", 0, 6, attributeName, 0, i)) {
// 'xml' is not a valid dialect prefix in HTML mode
return HTMLAttributeName.forName(null, attributeName);
}
return HTMLAttributeName.forName(
attributeName.substring(0, i - 1),
attributeName.substring(i,attributeName.length()));
}
if (!inData && c == '-') {
if (i == 5 && TextUtils.equals(TemplateMode.HTML.isCaseSensitive(), "data", 0, 4, attributeName, 0, 4)) {
inData = true;
continue;
} else {
// this is just a normal, non-thymeleaf 'data-*' attribute
return HTMLAttributeName.forName(null, attributeName);
}
}
if (inData && c == '-') {
if (i == 6) {
// '-' was the first char after 'data-', no prefix there
return HTMLAttributeName.forName(null, attributeName);
}
return HTMLAttributeName.forName(
attributeName.substring(5, i - 1),
attributeName.substring(i, attributeName.length()));
}
}
return HTMLAttributeName.forName(null, attributeName);
}
private static TextAttributeName buildTextAttributeName(final String prefix, final String attributeName) {
if (attributeName == null || attributeName.length() == 0) {
throw new IllegalArgumentException("Attribute name cannot be null or empty");
}
if (prefix == null || prefix.trim().length() == 0) {
return buildTextAttributeName(attributeName);
}
return TextAttributeName.forName(prefix, attributeName);
}
private static XMLAttributeName buildXMLAttributeName(final String prefix, final String attributeName) {
if (attributeName == null || attributeName.length() == 0) {
throw new IllegalArgumentException("Attribute name cannot be null or empty");
}
if (prefix == null || prefix.trim().length() == 0) {
return buildXMLAttributeName(attributeName);
}
return XMLAttributeName.forName(prefix, attributeName);
}
private static HTMLAttributeName buildHTMLAttributeName(final String prefix, final String attributeName) {
if (attributeName == null || attributeName.length() == 0) {
throw new IllegalArgumentException("Attribute name cannot be null or empty");
}
if (prefix == null || prefix.trim().length() == 0) {
return buildHTMLAttributeName(attributeName);
}
return HTMLAttributeName.forName(prefix, attributeName);
}
public static AttributeName forName(
final TemplateMode templateMode, final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (templateMode == null) {
throw new IllegalArgumentException("Template Mode cannot be null");
}
if (templateMode == TemplateMode.HTML) {
return forHTMLName(attributeNameBuffer, attributeNameOffset, attributeNameLen);
}
if (templateMode == TemplateMode.XML) {
return forXMLName(attributeNameBuffer, attributeNameOffset, attributeNameLen);
}
if (templateMode.isText()) {
return forTextName(attributeNameBuffer, attributeNameOffset, attributeNameLen);
}
throw new IllegalArgumentException("Unknown template mode '" + templateMode + "'");
}
public static AttributeName forName(final TemplateMode templateMode, final String attributeName) {
if (templateMode == null) {
throw new IllegalArgumentException("Template Mode cannot be null");
}
if (templateMode == TemplateMode.HTML) {
return forHTMLName(attributeName);
}
if (templateMode == TemplateMode.XML) {
return forXMLName(attributeName);
}
if (templateMode.isText()) {
return forTextName(attributeName);
}
throw new IllegalArgumentException("Unknown template mode '" + templateMode + "'");
}
public static AttributeName forName(final TemplateMode templateMode, final String prefix, final String attributeName) {
if (templateMode == null) {
throw new IllegalArgumentException("Template Mode cannot be null");
}
if (templateMode == TemplateMode.HTML) {
return forHTMLName(prefix, attributeName);
}
if (templateMode == TemplateMode.XML) {
return forXMLName(prefix, attributeName);
}
if (templateMode.isText()) {
return forTextName(prefix, attributeName);
}
throw new IllegalArgumentException("Unknown template mode '" + templateMode + "'");
}
public static TextAttributeName forTextName(final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (attributeNameBuffer == null || attributeNameLen == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
if (attributeNameOffset < 0 || attributeNameLen < 0) {
throw new IllegalArgumentException("Both name offset and length must be equal to or greater than zero");
}
return (TextAttributeName) textAttributeNamesRepository.getAttribute(attributeNameBuffer, attributeNameOffset, attributeNameLen);
}
public static XMLAttributeName forXMLName(final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (attributeNameBuffer == null || attributeNameLen == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
if (attributeNameOffset < 0 || attributeNameLen < 0) {
throw new IllegalArgumentException("Both name offset and length must be equal to or greater than zero");
}
return (XMLAttributeName) xmlAttributeNamesRepository.getAttribute(attributeNameBuffer, attributeNameOffset, attributeNameLen);
}
public static HTMLAttributeName forHTMLName(final char[] attributeNameBuffer, final int attributeNameOffset, final int attributeNameLen) {
if (attributeNameBuffer == null || attributeNameLen == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
if (attributeNameOffset < 0 || attributeNameLen < 0) {
throw new IllegalArgumentException("Both name offset and length must be equal to or greater than zero");
}
return (HTMLAttributeName) htmlAttributeNamesRepository.getAttribute(attributeNameBuffer, attributeNameOffset, attributeNameLen);
}
public static TextAttributeName forTextName(final String attributeName) {
if (attributeName == null || attributeName.trim().length() == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
return (TextAttributeName) textAttributeNamesRepository.getAttribute(attributeName);
}
public static XMLAttributeName forXMLName(final String attributeName) {
if (attributeName == null || attributeName.trim().length() == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
return (XMLAttributeName) xmlAttributeNamesRepository.getAttribute(attributeName);
}
public static HTMLAttributeName forHTMLName(final String attributeName) {
if (attributeName == null || attributeName.trim().length() == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
return (HTMLAttributeName) htmlAttributeNamesRepository.getAttribute(attributeName);
}
public static TextAttributeName forTextName(final String prefix, final String attributeName) {
if (attributeName == null || attributeName.trim().length() == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
return (TextAttributeName) textAttributeNamesRepository.getAttribute(prefix, attributeName);
}
public static XMLAttributeName forXMLName(final String prefix, final String attributeName) {
if (attributeName == null || attributeName.trim().length() == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
return (XMLAttributeName) xmlAttributeNamesRepository.getAttribute(prefix, attributeName);
}
public static HTMLAttributeName forHTMLName(final String prefix, final String attributeName) {
if (attributeName == null || attributeName.trim().length() == 0) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
return (HTMLAttributeName) htmlAttributeNamesRepository.getAttribute(prefix, attributeName);
}
private AttributeNames() {
super();
}
/*
* This repository class is thread-safe, as it will contain new instances of AttributeName created during
* processing (created when asking the repository for them when they do not exist yet). As any thread can
* create a new attribute, this has to be lock-protected.
*/
static final class AttributeNamesRepository {
private final TemplateMode templateMode;
private final List repositoryNames; // read-write, sync will be needed
private final List repository; // read-write, sync will be needed
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
private final Lock readLock = this.lock.readLock();
private final Lock writeLock = this.lock.writeLock();
AttributeNamesRepository(final TemplateMode templateMode) {
super();
this.templateMode = templateMode;
this.repositoryNames = new ArrayList(500);
this.repository = new ArrayList(500);
}
AttributeName getAttribute(final char[] text, final int offset, final int len) {
int index;
this.readLock.lock();
try {
/*
* First look for the element in the namespaced repository
*/
index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, text, offset, len);
if (index >= 0) {
return this.repository.get(index);
}
} finally {
this.readLock.unlock();
}
/*
* NOT FOUND. We need to obtain a write lock and store the text
*/
this.writeLock.lock();
try {
return storeAttribute(text, offset, len);
} finally {
this.writeLock.unlock();
}
}
AttributeName getAttribute(final String completeAttributeName) {
int index;
this.readLock.lock();
try {
/*
* First look for the element in the namespaced repository
*/
index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, completeAttributeName);
if (index >= 0) {
return this.repository.get(index);
}
} finally {
this.readLock.unlock();
}
/*
* NOT FOUND. We need to obtain a write lock and store the text
*/
this.writeLock.lock();
try {
return storeAttribute(completeAttributeName);
} finally {
this.writeLock.unlock();
}
}
AttributeName getAttribute(final String prefix, final String attributeName) {
int index;
this.readLock.lock();
try {
/*
* First look for the element in the namespaced repository
*/
index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, prefix, attributeName);
if (index >= 0) {
return this.repository.get(index);
}
} finally {
this.readLock.unlock();
}
/*
* NOT FOUND. We need to obtain a write lock and store the text
*/
this.writeLock.lock();
try {
return storeAttribute(prefix, attributeName);
} finally {
this.writeLock.unlock();
}
}
private AttributeName storeAttribute(final char[] text, final int offset, final int len) {
int index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, text, offset, len);
if (index >= 0) {
// It was already added while we were waiting for the lock!
return this.repository.get(index);
}
final AttributeName name;
if (this.templateMode == TemplateMode.HTML) {
name = buildHTMLAttributeName(text, offset, len);
} else if (this.templateMode == TemplateMode.XML) {
name = buildXMLAttributeName(text, offset, len);
} else { // this.templateMode.isText()
name = buildTextAttributeName(text, offset, len);
}
final String[] completeAttributeNames = name.completeAttributeNames;
for (final String completeAttributeName : completeAttributeNames) {
index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, completeAttributeName);
// binary Search returned (-(insertion point) - 1)
this.repositoryNames.add(((index + 1) * -1), completeAttributeName);
this.repository.add(((index + 1) * -1), name);
}
return name;
}
private AttributeName storeAttribute(final String attributeName) {
int index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, attributeName);
if (index >= 0) {
// It was already added while we were waiting for the lock!
return this.repository.get(index);
}
final AttributeName name;
if (this.templateMode == TemplateMode.HTML) {
name = buildHTMLAttributeName(attributeName);
} else if (this.templateMode == TemplateMode.XML) {
name = buildXMLAttributeName(attributeName);
} else { // this.templateMode.isText()
name = buildTextAttributeName(attributeName);
}
final String[] completeAttributeNames = name.completeAttributeNames;
for (final String completeAttributeName : completeAttributeNames) {
index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, completeAttributeName);
// binary Search returned (-(insertion point) - 1)
this.repositoryNames.add(((index + 1) * -1), completeAttributeName);
this.repository.add(((index + 1) * -1), name);
}
return name;
}
private AttributeName storeAttribute(final String prefix, final String attributeName) {
int index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, prefix, attributeName);
if (index >= 0) {
// It was already added while we were waiting for the lock!
return this.repository.get(index);
}
final AttributeName name;
if (this.templateMode == TemplateMode.HTML) {
name = buildHTMLAttributeName(prefix, attributeName);
} else if (this.templateMode == TemplateMode.XML) {
name = buildXMLAttributeName(prefix, attributeName);
} else { // this.templateMode.isText()
name = buildTextAttributeName(prefix, attributeName);
}
final String[] completeAttributeNames = name.completeAttributeNames;
for (final String completeAttributeName : completeAttributeNames) {
index = binarySearch(this.templateMode.isCaseSensitive(), this.repositoryNames, completeAttributeName);
// binary Search returned (-(insertion point) - 1)
this.repositoryNames.add(((index + 1) * -1), completeAttributeName);
this.repository.add(((index + 1) * -1), name);
}
return name;
}
private static int binarySearch(
final boolean caseSensitive, final List values, final char[] text, final int offset, final int len) {
int low = 0;
int high = values.size() - 1;
int mid, cmp;
String midVal;
while (low <= high) {
mid = (low + high) >>> 1;
midVal = values.get(mid);
cmp = TextUtils.compareTo(caseSensitive, midVal, 0, midVal.length(), text, offset, len);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
// Found!!
return mid;
}
}
return -(low + 1); // Not Found!! We return (-(insertion point) - 1), to guarantee all non-founds are < 0
}
private static int binarySearch(final boolean caseSensitive, final List values, final String text) {
int low = 0;
int high = values.size() - 1;
int mid, cmp;
String midVal;
while (low <= high) {
mid = (low + high) >>> 1;
midVal = values.get(mid);
cmp = TextUtils.compareTo(caseSensitive, midVal, text);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
// Found!!
return mid;
}
}
return -(low + 1); // Not Found!! We return (-(insertion point) - 1), to guarantee all non-founds are < 0
}
private static int binarySearch(final boolean caseSensitive,
final List values, final String prefix, final String attributeName) {
// This method will be specialized in finding prefixed attribute names (in the prefix:name form)
if (prefix == null || prefix.trim().length() == 0) {
return binarySearch(caseSensitive, values, attributeName);
}
final int prefixLen = prefix.length();
final int attributeNameLen = attributeName.length();
int low = 0;
int high = values.size() - 1;
int mid, cmp;
String midVal;
int midValLen;
while (low <= high) {
mid = (low + high) >>> 1;
midVal = values.get(mid);
midValLen = midVal.length();
if (TextUtils.startsWith(caseSensitive, midVal, prefix)) {
// Prefix matched, but it could be a mere coincidence if the text being evaluated doesn't have
// a ':' after the prefix letters, so we will make sure by comparing the next char manually
if (midValLen <= prefixLen) {
// midVal is exactly as prefix, therefore it goes first
low = mid + 1;
} else {
// Compare the next char
cmp = midVal.charAt(prefixLen) - ':';
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
// Prefix matches and we made sure midVal has a ':', so let's try the attributeName
cmp = TextUtils.compareTo(caseSensitive, midVal, prefixLen + 1, (midValLen - (prefixLen + 1)), attributeName, 0, attributeNameLen);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
// Found!!
return mid;
}
}
}
} else {
// midVal does not start with prefix, so comparing midVal and prefix should be enough
cmp = TextUtils.compareTo(caseSensitive, midVal, prefix);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
// This is impossible - if they were the same, we'd have detected it already!
throw new IllegalStateException("Bad comparison of midVal \"" + midVal + "\" and prefix \"" + prefix + "\"");
}
}
}
return -(low + 1); // Not Found!! We return (-(insertion point) - 1), to guarantee all non-founds are < 0
}
}
}