1package junitparams.naming;
2
3import junitparams.internal.TestMethod;
4import junitparams.internal.Utils;
5
6import java.util.Arrays;
7import java.util.HashSet;
8import java.util.Locale;
9import java.util.regex.Pattern;
10
11public class MacroSubstitutionNamingStrategy implements TestCaseNamingStrategy {
12    private static final String MACRO_PATTERN = "\\{[^\\}]{0,50}\\}";
13    // Pattern that keeps delimiters in split result
14    private static final Pattern MACRO_SPLIT_PATTERN = Pattern.compile(String.format("(?=%s)|(?<=%s)", MACRO_PATTERN, MACRO_PATTERN));
15    private static final String MACRO_START = "{";
16    private static final String MACRO_END = "}";
17    // Android-changed: CTS and AndroidJUnitRunner rely on specific format to test names, changing
18    // them will prevent CTS and AndroidJUnitRunner from working properly; see b/36541809
19    static final String DEFAULT_TEMPLATE = "{method}[{index}]";
20    private TestMethod method;
21
22    public MacroSubstitutionNamingStrategy(TestMethod testMethod) {
23        this.method = testMethod;
24    }
25
26    @Override
27    public String getTestCaseName(int parametersIndex, Object parameters) {
28        TestCaseName testCaseName = method.getAnnotation(TestCaseName.class);
29
30        String template = getTemplate(testCaseName);
31        String builtName = buildNameByTemplate(template, parametersIndex, parameters);
32
33        if (builtName.trim().isEmpty()) {
34            return buildNameByTemplate(DEFAULT_TEMPLATE, parametersIndex, parameters);
35        } else {
36            return builtName;
37        }
38    }
39
40    private String getTemplate(TestCaseName testCaseName) {
41        if (testCaseName != null) {
42            // Android-changed: CTS and AndroidJUnitRunner rely on specific format to test names,
43            // changing them will prevent CTS and AndroidJUnitRunner from working properly;
44            // see b/36541809
45            throw new IllegalStateException(
46                    "@TestCaseName not currently supported as it breaks running tests in CTS");
47            // return testCaseName.value();
48        }
49
50        return DEFAULT_TEMPLATE;
51    }
52
53    private String buildNameByTemplate(String template, int parametersIndex, Object parameters) {
54        StringBuilder nameBuilder = new StringBuilder();
55
56        String[] parts = MACRO_SPLIT_PATTERN.split(template);
57
58        for (String part : parts) {
59            String transformedPart = transformPart(part, parametersIndex, parameters);
60            nameBuilder.append(transformedPart);
61        }
62
63        return nameBuilder.toString();
64    }
65
66    private String transformPart(String part, int parametersIndex, Object parameters) {
67        if (isMacro(part)) {
68            return lookupMacroValue(part, parametersIndex, parameters);
69        }
70
71        return part;
72    }
73
74    private String lookupMacroValue(String macro, int parametersIndex, Object parameters) {
75        String macroKey = getMacroKey(macro);
76
77        switch (Macro.parse(macroKey)) {
78            case INDEX: return String.valueOf(parametersIndex);
79            case PARAMS: return Utils.stringify(parameters);
80            case METHOD: return method.name();
81            default: return substituteDynamicMacro(macro, macroKey, parameters);
82        }
83    }
84
85    private String substituteDynamicMacro(String macro, String macroKey, Object parameters) {
86        if (isMethodParameterIndex(macroKey)) {
87            int index = parseIndex(macroKey);
88            return Utils.getParameterStringByIndexOrEmpty(parameters, index);
89        }
90
91        return macro;
92    }
93
94    private boolean isMethodParameterIndex(String macroKey) {
95        return macroKey.matches("\\d+");
96    }
97
98    private int parseIndex(String macroKey) {
99        return Integer.parseInt(macroKey);
100    }
101
102    private String getMacroKey(String macro) {
103        return macro
104                .substring(MACRO_START.length(), macro.length() - MACRO_END.length())
105                .toUpperCase(Locale.ENGLISH);
106    }
107
108    private boolean isMacro(String part) {
109        return part.startsWith(MACRO_START) && part.endsWith(MACRO_END);
110    }
111
112    private enum Macro {
113        INDEX,
114        PARAMS,
115        METHOD,
116        NONE;
117
118        public static Macro parse(String value) {
119            if (macros.contains(value)) {
120                return Macro.valueOf(value);
121            } else {
122                return Macro.NONE;
123            }
124        }
125
126        private static final HashSet<String> macros = new HashSet<String>(Arrays.asList(
127                Macro.INDEX.toString(), Macro.PARAMS.toString(), Macro.METHOD.toString())
128        );
129    }
130}
131