All Downloads are FREE. Search and download functionalities are using the official Maven repository.

TeamControlium.Utilities.Detokenizer Maven / Gradle / Ivy

Go to download

TeamControlium Utilities provides a set of Test Framework utilities for use within the Team Controlium suite

There is a newer version: 1.2.3
Show newest version
package TeamControlium.Utilities;

import org.apache.commons.lang.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;

//
// Examples;
//
// {random;date(01-11-2000,31-12-2001);d/M/yyyy}   - Returns a date between 01-11-2000 & 31-12-2001 (inclusive)
//  EG.  2/7/2001
//
// {random;float(1.34,29.78);%.4f} - Returns a floating point number to 4 decimal places between 1.34,29.78 (inclusive)
// EG. 12.7674
//
// {random;from(ASsDF1234);5} - Returns a random set of 5 characters from the charcters given
// EG. D3sF1
//
// {random;letters,5} - Returns 3 random letters
// EG. fGt
//
// {random;uppercaseletters;5} - Returns 5 random uppercase letters
// EG. GBHWP
//
// {random;lowercaseletters;6} - Returns 6 random lowercase letters
// EG. bjhaoud
//
// {random;digits;4} - Returns 4 random digits
// EG. 7890
//
// {random;acn} - returns a valid random Australian Company Number
// EG. 239878405
//
//

public class Detokenizer {
    private static final char tokenStartChar = '{';
    private static final char tokenEndChar = '}';

    public static void setCustomTokenProcessor(BiFunction < String, String[], String > customProcessor) {
        _CustomTokenProcessor = customProcessor;
    }
    public static String ProcessTokensInString(String stringWithTokens) {
        String detokenizedString = "";
        int startIndex = 0;
        boolean foundTokenStart = false;

        //
        // Find the start of a token, ignoring doubles {{'s as they are litterals)
        //
        while (!foundTokenStart && startIndex < stringWithTokens.length()) {
            if (stringWithTokens.charAt(startIndex) == tokenStartChar) {
                // We are looking at a token start char...
                if ((startIndex < stringWithTokens.length() - 1) && (stringWithTokens.charAt(startIndex + 1) == tokenStartChar)) {
                    // Next char is also a start char, so ignore and skip past
                    startIndex += 1;
                } else {
                    // Next char not a start char so we have found a token!
                    foundTokenStart = true;
                }
            }
            startIndex += 1;
        }

        //
        // startIndex is now pointing to first char of a token
        //

        if (foundTokenStart) {
            boolean foundTokenEnd = false;
            int endIndex = startIndex; // We start searching for the end of the token from the first character of the
            //
            // Find the end of the token.
            //
            while (!foundTokenEnd && endIndex < stringWithTokens.length()) {
                if ((stringWithTokens.charAt(endIndex) == tokenStartChar) &&
                        !((startIndex < stringWithTokens.length() - 1) && (stringWithTokens.charAt(startIndex + 1) == tokenStartChar))) {
                    //
                    // Another start token (and it is NOT a dounble!!!!)  We have nested tokens by golly.
                    // So, start the process again, but from the new start of the nested token. Hah, this
                    // is a quick easy way of dealing with nested tokens!
                    //
                    startIndex = endIndex + 1;
                } else if (stringWithTokens.charAt(endIndex) == tokenEndChar) {
                    if ((endIndex < stringWithTokens.length() - 1) && (stringWithTokens.charAt(endIndex + 1) == tokenEndChar)) {
                        // Next char is also an end char, so ignore and skip past
                        endIndex += 1;
                    } else {
                        // Next char not a start char so we have found a token!
                        foundTokenEnd = true;
                    }
                }
                endIndex += 1;
            }
            if (foundTokenEnd) {
                detokenizedString += stringWithTokens.substring(0, startIndex - 1);
                String token = stringWithTokens.substring(startIndex, endIndex - 1);
                try {
                    detokenizedString += ProcessToken(token);
                } catch (Exception ex) {
                    Logger.WriteLine(Logger.LogLevels.Error,String.format("Error processing token {%s}: %s", token, ex));
                    detokenizedString += String.format("",token);
                }
                detokenizedString += stringWithTokens.substring(endIndex, stringWithTokens.length());
            } else {
                Logger.WriteLine(Logger.LogLevels.Error,String.format("Found token start {{ found at index {%d} but no closing }} found: [%s]", startIndex, stringWithTokens));
            }
            // Now, we call ourself again to process any more tokens....
            detokenizedString = ProcessTokensInString(detokenizedString);
        } else {
            // So no token found. We will convert all doubles back to singles and return the string...
            detokenizedString += stringWithTokens.replace("{{", "{").replace("}}", "}");
        }
        return detokenizedString;
    }

    /// 
    /// System delegate to do custom token processing
    /// 
    private static BiFunction < String, String[], String > _CustomTokenProcessor = null;

    private static String ProcessToken(String token) throws Exception {
        String delimiter = ";";
        String processedToken = null;
        if (StringUtils.isEmpty(token)) throw new Exception("Empty token!");
        String[] splitToken = token.split(delimiter, 2);

        //
        // First, we try using the customer processor.  We do this first so that the test framework can override any
        // of the built-in token processors if needed
        if (_CustomTokenProcessor != null) {
            try {
                processedToken = _CustomTokenProcessor.apply(delimiter, splitToken);
            } catch (Exception ex) {
                throw new Exception("Error thrown by custom token processor.", ex);
            }
        }
        if (processedToken == null) {
            switch (splitToken[0].toLowerCase().trim()) {
                case "random":
                    if (splitToken.length < 2)
                        throw new Exception("Random token [" + token + "] needs 3 parts {{random;;}}");
                    processedToken = DoRandomToken(delimiter, splitToken[1]);
                    break;
                case "date":
                    if (splitToken.length < 2)
                        throw new Exception("Date token [" + token + "] needs 3 parts {{date;;}}");
                    processedToken = DoDateToken(delimiter, splitToken[1]);
                    break;
                case "financialyearstart":
                    if (splitToken.length < 2)
                        throw new Exception("FinancialYearStart token [" + token + "] needs 3 parts {{FinancialYearStart;;}}");
                    processedToken = DoFinancialYearToken(delimiter, splitToken[1], true);
                    break;
                case "financialyearend":
                    if (splitToken.length < 2)
                        throw new Exception("FinancialYearEnd token [" + token + "] needs 3 parts {{FinancialYearEnd;;}}");
                    processedToken = DoFinancialYearToken(delimiter, splitToken[1], false);
                    break;
                default: {
                    throw new Exception("Unsupported token [" + splitToken[0] + "] in [" + token + "]");
                }
            }
        }
        return processedToken;
    }
    private static String DoRandomToken(String delimiter, String TypeAndLength) throws Exception {
        String[] typeAndLengthOrFormat = TypeAndLength.split(delimiter, 2);
        String result = "";
        String select = "";
        String verb = typeAndLengthOrFormat[0].toLowerCase().trim();

        if (verb.startsWith("date(")) {
            result = new SimpleDateFormat(typeAndLengthOrFormat[1]).format(DoRandomDate(verb.substring(verb.indexOf('(') + 1, verb.length() - 1)));
        } else if (verb.startsWith("float(")) {
            result = String.format(typeAndLengthOrFormat[1], DoRandomFloat(verb.substring(verb.indexOf('(') + 1, verb.length() - 1)));
        } else {
            // {random,from(ASDF),5} - 5 characters selected from ASDF
            if (verb.startsWith("from(")) {
                select = typeAndLengthOrFormat[0].trim().substring(verb.indexOf('(') + 1, verb.length() - 2);
            } else {
                switch (verb) {
                    case "letters":
                        select = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
                        break;
                    case "lowercaseletters":
                        select = "abcdefghijklmnopqrstuvwxyz";
                        break;
                    case "uppercaseletters":
                        select = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                        break;
                    case "digits":
                        select = "01234567890";
                        break;
                    case "alphanumerics":
                        select = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890";
                        break;
                    default:
                        throw new Exception("Unrecognised random Type [" + typeAndLengthOrFormat[0] + "] - Expect letters, lowercaseletters, uppercaseletters digits or alphanumerics");
                }
            }
            int number;
            try {
                int lastDigit = 0;
                while (lastDigit < typeAndLengthOrFormat[1].length() && Character.isDigit(typeAndLengthOrFormat[1].charAt(lastDigit))) lastDigit++;
                number = Integer.parseInt(typeAndLengthOrFormat[1].substring(0, lastDigit));
            } catch (Exception ex) {
                throw new Exception("Invalid number of characters in Random token {{random;;}}");
            }
            for (int index = 0; index < number; index++) {
                int selectIndex = ThreadLocalRandom.current().nextInt(0, select.length());
                result += select.charAt(selectIndex);
            }
        }
        return result;
    }
    private static float DoRandomFloat(String MaxAndMinFloats) throws Exception {
        String delimiter = ",";
        String[] MaxAndMin = MaxAndMinFloats.split(delimiter);
        if (MaxAndMin.length != 2)
            throw new Exception("Invalid Maximum and Minimum floats. Expect {{random.float(min;max),}}. Max/min was: [" + MaxAndMinFloats + "]");
        float Min;
        float Max;

        try {
            Min = Float.parseFloat(MaxAndMin[0]);
        } catch (Exception ex) {
            throw new Exception("Invalid Minimum float. Expect {{random.float(min;max),}}. Max/min was: [" + MaxAndMinFloats + "]");
        }
        try {
            Max = Float.parseFloat(MaxAndMin[1]);
        } catch (Exception ex) {
            throw new Exception("Invalid Maximum float. Expect {{random.float(min;max),}}. Max/min was: [" + MaxAndMinFloats + "]");
        }
        return DoRandomFloat(Min, Max);
    }
    private static float DoRandomFloat(float MinFloat, float MaxFloat) throws Exception {
        if (MinFloat >= MaxFloat)
            throw new Exception("Maximum float less than Minimum float! Expect {{random.float(min,max),}} Min = " + Float.toString(MinFloat) + ", Max = " + Float.toString(MaxFloat));
        return ThreadLocalRandom.current().nextFloat() * (MaxFloat - MinFloat) + MinFloat;
    }
    private static Date DoRandomDate(String MaxAndMinDates) throws Exception {
        String delimiter = ",";
        String[] MaxAndMin = MaxAndMinDates.split(delimiter);
        if (MaxAndMin.length != 2)
            throw new Exception("Invalid Maximum and Minimum dates. Expect {{random;date(dd-MM-yyyy,dd-MM-yyyy);}}. Max/min was: [" + MaxAndMinDates + "]");
        Date Min;
        Date Max;
        SimpleDateFormat dateFormat = new SimpleDateFormat("d-M-yyyy");

        try {
            Min = dateFormat.parse(MaxAndMin[0]);
        } catch (Exception ex) {
            throw new Exception("Invalid Minimum date. Expect {{random;date(dd-MM-yyyy,dd-MM-yyyy);}}. Max/min was: [" + MaxAndMinDates + "]");
        }
        try {
            Max = dateFormat.parse(MaxAndMin[1]);
        } catch (Exception ex) {
            throw new Exception("Invalid Maximum date. Expect {{random;date(dd-MM-yyyy,dd-MM-yyyy);}}. Max/min was: [" + MaxAndMinDates + "]");
        }

        return DoRandomDate(Min, Max);
    }
    private static Date DoRandomDate(Date MinDate, Date MaxDate) throws Exception {
        if (MinDate.after(MaxDate)) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("d-M-yyyy");
            throw new Exception("Maximum date earlier than Maximum date! Expect {{random;date(dd-MM-yyyy,dd-MM-yyyy);}} Mindate = " + dateFormat.format(MinDate) + ", Maxdate = " + dateFormat.format(MaxDate));
        }
        return new Date(ThreadLocalRandom.current().nextLong(MinDate.getTime(), MaxDate.getTime()));
    }
    private static String DoDateToken(String delimiter, String OffsetAndFormat) throws Exception {
        String[] offsetAndFormat = OffsetAndFormat.split(delimiter, 2);

        if (offsetAndFormat.length != 2) {
            throw new Exception("Date token does not have a format parameter; example: {date" + delimiter + "today" + delimiter + "dd-MM-yyyy}");
        }
        Date dt;
        String verb = offsetAndFormat[0].toLowerCase().trim();
        if (verb.startsWith("random(")) {
            dt = DoRandomDate(verb.substring(verb.indexOf('(') + 1, verb.length() - 2 - verb.indexOf('(')));
        } else {
            switch (verb) {
                case "today":
                    dt = new Date();

                    break;
                case "yesterday":
                    dt = getDateOffset(Calendar.DATE, -1);
                    break;
                case "tomorrow":
                    dt = getDateOffset(Calendar.DATE, +1);
                    break;
                default:
                {
                    String[] activeOffset = verb.substring(0, verb.length() - 1).split(Pattern.quote("("), 2);
                    if (offsetAndFormat[0].contains("(") && offsetAndFormat[0].endsWith(")")) {
                        int offset;
                        try {
                            offset = Integer.parseInt(activeOffset[1]);
                        } catch (Exception ex) {
                            throw new Exception("Invalid Active Date offset.  Expect AddYears(n) AddMonths(n) or AddDays(n). Got [" + activeOffset[0].trim() + "]");
                        }


                        switch (activeOffset[0].trim()) {
                            case "addyears":
                                dt = getDateOffset(Calendar.YEAR, offset);
                                break;
                            case "addmonths":
                                dt = getDateOffset(Calendar.MONTH, offset);
                                break;
                            case "adddays":
                                dt = getDateOffset(Calendar.DATE, offset);
                                break;
                            default:
                                throw new Exception("Invalid Active Date offset.  Expect AddYears(n) AddMonths(n) or AddDays(n). Got [" + activeOffset[0].trim() + "]");
                        }
                    } else {
                        throw new Exception("Invalid Active Date offset.  Expect AddYears(n) AddMonths(n) or AddDays(n). Got [" + activeOffset[0].trim() + "]");
                    }
                    break;
                }
            }
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat(offsetAndFormat[1]);
        return dateFormat.format(dt);
    }
    private static String DoFinancialYearToken(String delimiter, String DateToWorkFromAndFormat, boolean Start) throws Exception {
        String financialYearStart = "01/07";
        String financialYearEnd = "01/07";

        String[] dateToWorkFromAndFormat = DateToWorkFromAndFormat.split(delimiter, 2);

        SimpleDateFormat dateFormat = new SimpleDateFormat();

        Date dateToWorkFrom = tryParseDate(dateToWorkFromAndFormat[0], Arrays.asList("dd/MM/yyyy", "d/MM/yyyy", "dd/M/yyyy", "d/M/yyyy", "dd/MM/yy", "d/MM/yy", "dd/M/yy", "d/M/yy"));

        if (dateToWorkFrom == null) {
            throw new Exception("Cannot parse date [" + dateToWorkFromAndFormat[0] + "].  Must be in format d/M/y.");
        }

        String year;
        if (dateToCalendar(dateToWorkFrom).get(Calendar.MONTH) + 1 >= 7)
            year = Integer.toString(Start ? dateToCalendar(dateToWorkFrom).get(Calendar.YEAR) : dateToCalendar(dateToWorkFrom).get(Calendar.YEAR));
        else
            year = Integer.toString(Start ? (dateToCalendar(dateToWorkFrom).get(Calendar.YEAR) - 1) : dateToCalendar(dateToWorkFrom).get(Calendar.YEAR));

        Date returnDate = tryParseDate((Start ? financialYearStart : financialYearEnd) + "/" + year, Arrays.asList("dd/MM/yyyy"));

        return new SimpleDateFormat(dateToWorkFromAndFormat[1]).format(returnDate);
    }
    private static Date getDateOffset(int offsetType, int offset) {
        final Calendar cal = Calendar.getInstance();
        cal.add(offsetType, offset);
        return cal.getTime();
    }
    private static Calendar dateToCalendar(Date date) {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal;
    }
    private static Date tryParseDate(String dateString, List < String > validFormats) {
        for (String formatString: validFormats) {
            try {
                return new SimpleDateFormat(formatString).parse(dateString);
            } catch (ParseException ex) {}
        }
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy