/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.service.history;
/**
* Object used to uniquely identify a group of history records.
*
* @author Alexander Pelov
*/
public class HistoryID
{
private final String[] id;
private final String stringRepresentation;
private final int hashCode;
private HistoryID(String[] id)
{
this.id = id;
StringBuilder buff = new StringBuilder();
for (int i = 0; i < id.length; i++)
{
if (i > 0)
buff.append(' ');
buff.append(this.id[i]);
}
this.stringRepresentation = buff.toString();
this.hashCode = this.stringRepresentation.hashCode();
}
/**
* Create a HistoryID from a raw ID. You can pass any kind of strings and
* they will be safely converted to valid IDs.
*/
public static HistoryID createFromRawID(String[] rawid)
{
// TODO: Validate: Assert.assertNonNull(rawid, "Parameter RAWID should
// be non-null");
// TODO: Validate: Assert.assertTrue(rawid.length > 0, "RAWID.length
// should be > 0");
String[] id = new String[rawid.length];
for (int i = 0; i < rawid.length; i++)
{
id[i] = HistoryID.readableHash(rawid[i]);
}
return new HistoryID(id);
}
/**
* Create a HistoryID from a raw Strings. You can pass any kind of strings
* and they will be checked and converted to valid IDs.
*/
public static HistoryID createFromRawStrings(String[] rawStrings)
{
String[] id = new String[rawStrings.length];
for (int i = 0; i < rawStrings.length; i++)
{
id[i] = HistoryID.decodeReadableHash(rawStrings[i]);
}
return new HistoryID(id);
}
/**
* Create a HistoryID from a valid ID. You should pass only valid IDs (ones
* produced from readableHash).
*
* @throws IllegalArgumentException
* Thrown if a string from the ID is not valid an exception.
*/
public static HistoryID createFromID(String[] id)
throws IllegalArgumentException
{
// TODO: Validate: Assert.assertNonNull(id, "Parameter ID should be
// non-null");
// TODO: Validate: Assert.assertTrue(id.length > 0, "ID.length should be
// > 0");
for (int i = 0; i < id.length; i++)
{
if (!HistoryID.isIDValid(id[i]))
{
throw new IllegalArgumentException("Not a valid ID: " + id[i]);
}
}
String[] newID = new String[id.length];
System.arraycopy(id, 0, newID, 0, id.length);
return new HistoryID(newID);
}
public String[] getID()
{
return this.id;
}
@Override
public String toString()
{
return this.stringRepresentation;
}
@Override
public int hashCode()
{
return this.hashCode;
}
@Override
public boolean equals(Object obj)
{
boolean eq = false;
if (obj instanceof HistoryID)
{
String[] id = ((HistoryID) obj).id;
if (this.id.length == id.length)
{
eq = true;
for (int i = 0; i < id.length; i++)
{
String s1 = id[i];
String s2 = this.id[i];
if (s1 != s2 && (s1 == null || !s1.equals(s2)))
{
eq = false;
break;
}
}
}
}
return eq;
}
/**
* An one-way function returning a "human readable" containing no special
* characters. All characters _, a-z, A-Z, 0-9 are kept unchainged. All
* other are replaced with _ and the word is postfixed with $HASHCODE, where
* HASHCODE is the hexadecimal hash value of the original string. If there
* are no special characters the word is not postfixed.
*
* Note: This method does not use URLEncoder, because in url-encoding the *
* sign is considered as "safe".
*
* @param rawString
* The string to be hashed.
* @return The human-readable hash.
*/
public static String readableHash(String rawString)
{
StringBuilder encodedString = new StringBuilder(rawString);
boolean addHash = false;
for (int i = 0; i < encodedString.length(); i++)
{
if (HistoryID.isSpecialChar(encodedString.charAt(i)))
{
addHash = true;
encodedString.setCharAt(i, '_');
}
}
if (addHash)
{
encodedString.append('$');
encodedString.append(Integer.toHexString(rawString.hashCode()));
}
return encodedString.toString();
}
/**
* Decodes readable hash.
*
* @param rawString The string to be checked.
* @return The human-readable hash.
*/
public static String decodeReadableHash(String rawString)
{
int replaceCharIx = rawString.indexOf("_");
int hashCharIx = rawString.indexOf("$");
if(replaceCharIx > -1
&& hashCharIx > -1
&& replaceCharIx < hashCharIx)
{
//String rawStrNotHashed = encodedString.substring(0, hashCharIx);
// String hashValue = encodedString.substring(hashCharIx + 1);
// TODO: we can check the string, just to be sure, if we now
// the char to replace, when dealing with accounts it will be :
return rawString;
}
else
return rawString;
}
/**
* Tests if an ID is valid.
*/
private static boolean isIDValid(String id)
{
boolean isValid = true;
int pos = id.indexOf('$');
if (pos < 0)
{
// There is no $ in the id. In order to be valid all characters
// should be non-special
isValid = !hasSpecialChar(id);
} else {
// There is a $ sign in the id. In order to be valid it has
// to be in the form X..X$Y..Y, where there should be no
// special characters in X..X, and Y..Y should be a hexadecimal
// number
if (pos + 1 < id.length())
{
String start = id.substring(0, pos);
String end = id.substring(pos + 1);
// Check X..X
isValid = !hasSpecialChar(start);
if (isValid)
{
// OK; Check Y..Y
try
{
Integer.parseInt(end, 16);
// OK
isValid = true;
}
catch (Exception e)
{
// Not OK
isValid = false;
}
}
} else {
// The % sign is in the beginning - bad ID.
isValid = false;
}
}
return isValid;
}
/**
* Tests if a character is a special one. A character is special if it is
* not in the range _, a-z, A-Z, 0-9.
*
* @param c
* The character to test.
* @return Returns true if the character is special. False otherwise.
*/
private static boolean isSpecialChar(char c)
{
return (c != '_')
&& (c != '@')
&& (c != '.')
&& (c != '-')
&& (c != '+')
&& (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')
&& (c < '0' || c > '9');
}
/**
* Tests there is a special character in a string.
*/
private static boolean hasSpecialChar(String str)
{
boolean hasSpecialChar = false;
for (int i = 0; i < str.length(); i++)
{
if (isSpecialChar(str.charAt(i)))
{
hasSpecialChar = true;
break;
}
}
return hasSpecialChar;
}
}