blob: 50aff05fc0351bff455a0e37752f3dd009ffbb0c [file] [log] [blame]
using J2N.Numerics;
using J2N.Text;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Lucene.Net.Util
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/// <summary>
/// Methods for manipulating strings.
/// <para/>
/// @lucene.internal
/// </summary>
public static class StringHelper // LUCENENET specific - marked static and removed private constructor
{
/// <summary>
/// Pass this as the seed to <see cref="Murmurhash3_x86_32(byte[], int, int, int)"/>. </summary>
//Singleton-esque member. Only created once
public static readonly int GOOD_FAST_HASH_SEED = InitializeHashSeed();
// Poached from Guava: set a different salt/seed
// for each JVM instance, to frustrate hash key collision
// denial of service attacks, and to catch any places that
// somehow rely on hash function/order across JVM
// instances:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InitializeHashSeed()
{
// LUCENENET specific - reformatted with :
string prop = SystemProperties.GetProperty("tests:seed", null);
if (prop != null)
{
// So if there is a test failure that relied on hash
// order, we remain reproducible based on the test seed:
if (prop.Length > 8)
{
prop = prop.Substring(prop.Length - 8);
}
return Convert.ToInt32(prop, 16);
}
else
{
return (int)J2N.Time.CurrentTimeMilliseconds();
}
}
/// <summary>
/// Compares two <see cref="BytesRef"/>, element by element, and returns the
/// number of elements common to both arrays.
/// </summary>
/// <param name="left"> The first <see cref="BytesRef"/> to compare. </param>
/// <param name="right"> The second <see cref="BytesRef"/> to compare. </param>
/// <returns> The number of common elements. </returns>
public static int BytesDifference(this BytesRef left, BytesRef right) // LUCENENET specific - converted to extension method
{
int len = left.Length < right.Length ? left.Length : right.Length;
var bytesLeft = left.Bytes;
int offLeft = left.Offset;
var bytesRight = right.Bytes;
int offRight = right.Offset;
for (int i = 0; i < len; i++)
{
if (bytesLeft[i + offLeft] != bytesRight[i + offRight])
{
return i;
}
}
return len;
}
/// <summary> Returns a <see cref="T:IComparer{string}"/> over versioned strings such as X.YY.Z
/// <para/>
/// @lucene.internal
/// </summary>
public static IComparer<string> VersionComparer { get; } = Comparer<string>.Create((a, b) =>
{
var aTokens = new StringTokenizer(a, ".");
var bTokens = new StringTokenizer(b, ".");
while (aTokens.MoveNext())
{
int aToken = Convert.ToInt32(aTokens.Current, CultureInfo.InvariantCulture);
if (bTokens.MoveNext())
{
int bToken = Convert.ToInt32(bTokens.Current, CultureInfo.InvariantCulture);
if (aToken != bToken)
{
return aToken < bToken ? -1 : 1;
}
}
else
{
// a has some extra trailing tokens. if these are all zeroes, thats ok.
if (aToken != 0)
{
return 1;
}
}
}
// b has some extra trailing tokens. if these are all zeroes, thats ok.
while (bTokens.MoveNext())
{
if (Convert.ToInt32(bTokens.Current, CultureInfo.InvariantCulture) != 0)
{
return -1;
}
}
return 0;
});
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Equals(string s1, string s2)
{
if (s1 == null)
{
return s2 == null;
}
else
{
return s1.Equals(s2, StringComparison.Ordinal);
}
}
/// <summary>
/// Returns <c>true</c> if the <paramref name="ref"/> starts with the given <paramref name="prefix"/>.
/// Otherwise <c>false</c>.
/// </summary>
/// <param name="ref">
/// The <see cref="BytesRef"/> to test. </param>
/// <param name="prefix">
/// The expected prefix </param>
/// <returns> Returns <c>true</c> if the <paramref name="ref"/> starts with the given <paramref name="prefix"/>.
/// Otherwise <c>false</c>. </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWith(this BytesRef @ref, BytesRef prefix) // LUCENENET specific - converted to extension method
{
return SliceEquals(@ref, prefix, 0);
}
/// <summary>
/// Returns <c>true</c> if the <paramref name="ref"/> ends with the given <paramref name="suffix"/>. Otherwise
/// <c>false</c>.
/// </summary>
/// <param name="ref">
/// The <see cref="BytesRef"/> to test. </param>
/// <param name="suffix">
/// The expected suffix </param>
/// <returns> Returns <c>true</c> if the <paramref name="ref"/> ends with the given <paramref name="suffix"/>.
/// Otherwise <c>false</c>. </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWith(this BytesRef @ref, BytesRef suffix) // LUCENENET specific - converted to extension method
{
return SliceEquals(@ref, suffix, @ref.Length - suffix.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool SliceEquals(this BytesRef sliceToTest, BytesRef other, int pos) // LUCENENET specific - converted to extension method
{
if (pos < 0 || sliceToTest.Length - pos < other.Length)
{
return false;
}
int i = sliceToTest.Offset + pos;
int j = other.Offset;
int k = other.Offset + other.Length;
while (j < k)
{
if (sliceToTest.Bytes[i++] != other.Bytes[j++])
{
return false;
}
}
return true;
}
/// <summary>
/// Returns the MurmurHash3_x86_32 hash.
/// Original source/tests at <a href="https://github.com/yonik/java_util/">https://github.com/yonik/java_util/</a>.
/// </summary>
public static int Murmurhash3_x86_32(byte[] data, int offset, int len, int seed)
{
const int c1 = unchecked((int)0xcc9e2d51);
const int c2 = 0x1b873593;
int h1 = seed;
int roundedEnd = offset + (len & unchecked((int)0xfffffffc)); // round down to 4 byte block
for (int i = offset; i < roundedEnd; i += 4)
{
// little endian load order
int k1 = (((sbyte)data[i]) & 0xff) | ((((sbyte)data[i + 1]) & 0xff) << 8) | ((((sbyte)data[i + 2]) & 0xff) << 16) | (((sbyte)data[i + 3]) << 24);
k1 *= c1;
k1 = BitOperation.RotateLeft(k1, 15);
k1 *= c2;
h1 ^= k1;
h1 = BitOperation.RotateLeft(h1, 13);
h1 = h1 * 5 + unchecked((int)0xe6546b64);
}
// tail
int k2 = 0;
switch (len & 0x03)
{
case 3:
k2 = (((sbyte)data[roundedEnd + 2]) & 0xff) << 16;
// fallthrough
goto case 2;
case 2:
k2 |= (((sbyte)data[roundedEnd + 1]) & 0xff) << 8;
// fallthrough
goto case 1;
case 1:
k2 |= (((sbyte)data[roundedEnd]) & 0xff);
k2 *= c1;
k2 = BitOperation.RotateLeft(k2, 15);
k2 *= c2;
h1 ^= k2;
break;
}
// finalization
h1 ^= len;
// fmix(h1);
h1 ^= h1.TripleShift(16);
h1 *= unchecked((int)0x85ebca6b);
h1 ^= h1.TripleShift(13);
h1 *= unchecked((int)0xc2b2ae35);
h1 ^= h1.TripleShift(16);
return h1;
}
/// <summary>
/// Returns the MurmurHash3_x86_32 hash.
/// Original source/tests at <a href="https://github.com/yonik/java_util/">https://github.com/yonik/java_util/</a>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Murmurhash3_x86_32(BytesRef bytes, int seed)
{
return Murmurhash3_x86_32(bytes.Bytes, bytes.Offset, bytes.Length, seed);
}
}
}