1 // © 2019 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 package org.unicode.icu.tool.cldrtoicu; 4 5 import static com.google.common.base.Preconditions.checkNotNull; 6 7 import java.util.function.Function; 8 9 import org.unicode.cldr.api.CldrPath; 10 import org.unicode.cldr.api.CldrValue; 11 12 import com.google.common.collect.ImmutableList; 13 14 /** 15 * API for transforming CLDR path/value pairs. Transformed results support grouping by their key 16 * and the ability to generate default "fallback" values to account for missing values in a group. 17 * 18 * <p>To transform some set of CLDR path/values: 19 * <ol> 20 * <li>Transform all desired path/value pairs into a set of matched results, discarding duplicates 21 * (see {@link #transform(CldrValue)}. 22 * <li>Group the results by key (e.g. into a {@code ListMultimap}). 23 * <li>For each group, add any fallback values which don't yet exist for that key (see 24 * {@link #getFallbackResultsFor(RbPath, DynamicVars)} and {@link Result#isFallbackFor(Result)}). 25 * <li>Sort elements within each group and flatten result values (see {@link Result#isGrouped()}). 26 * </ol> 27 * 28 * <p>For each unique key, this should yield correctly ordered sequence of values (according to the 29 * semantics of the chosen transformer implementation). 30 */ 31 public abstract class PathValueTransformer { 32 /** 33 * A result either obtained by transforming a path/value pair, or as a potential fallback for 34 * some known key (see {@link PathValueTransformer#transform(CldrValue)} and 35 * {@link PathValueTransformer#getFallbackResultsFor(RbPath, DynamicVars)}). 36 */ 37 public static abstract class Result implements Comparable<Result> { 38 private final RbPath key; 39 Result(RbPath key)40 protected Result(RbPath key) { 41 this.key = checkNotNull(key); 42 } 43 44 /** 45 * Returns the key of this result, used to group results and determine fallback values 46 * according to the semantics of the chosen transformer. 47 */ getKey()48 public RbPath getKey() { 49 return key; 50 } 51 52 /** 53 * Returns whether the values in this result should be grouped or not. Un-grouped values 54 * should be considered as individual values in a sequence and might be joined with values 55 * from other results in the same group. Grouped values cannot be split and must appear 56 * as a single value. 57 * 58 * <p>For example for the ordered results: 59 * <pre> 60 * Result X = { key=K, values=[ "a", "b" ], grouped=false } 61 * Result Y = { key=K, values=[ "c", "d" ], grouped=false } 62 * Result Z = { key=K, values=[ "e" ], grouped=false } 63 * </pre> 64 * the values for key {@code K} are conceptually {@code [ "a", "b", "c", "d", "e" ]}. 65 * 66 * <p>However if result {@code Y} has {@code grouped=true} then there are now 4 values 67 * {@code [ "a", "b", ["c", "d"], "e" ]}, and if {@code X} is also grouped, then it is 68 * {@code [ ["a", "b"], ["c", "d"], "e" ]}, producing only 3 top-level values. 69 */ isGrouped()70 public abstract boolean isGrouped(); 71 72 /** 73 * Returns the transformed values of this result, which may or may not be grouped 74 * according to {@link #isGrouped()}. 75 */ getValues()76 public abstract ImmutableList<String> getValues(); 77 78 /** 79 * Returns whether this result is a fallback for some existing matched result. Fallback 80 * results should only be used when it is not a fallback for any existing result. 81 */ isFallbackFor(Result r)82 public abstract boolean isFallbackFor(Result r); 83 84 /** Debug only string representation. */ 85 @Override toString()86 public final String toString() { 87 return String.format( 88 "Result{ key='%s', grouped=%s, values=%s }", 89 getKey(), isGrouped(), getValues()); 90 } 91 } 92 93 /** 94 * A "typedef" for the function to do late binding of dynamic variables. This is used for edge 95 * cases where a %N variable in the rules config is bound to a CLDR path (e.g. "//foo/bar") 96 * which cannot be resolved until the rule is evaluated. Unfortunately the need to support late 97 * binding of variables incurs significant additional complexity in the code, despite being 98 * used in exactly one situation so far (the '%D' variable to represent the default numbering 99 * scheme. 100 */ 101 // TODO: Figure out how to get rid of all of this mess. 102 public interface DynamicVars extends Function<CldrPath, String> {} 103 104 /** 105 * Transforms a CLDR value into a sequence of results (empty if the value was not matched by 106 * any rule). 107 * 108 * @param cldrValue the value to transform. 109 * @return the transformed result(s). 110 */ transform(CldrValue cldrValue)111 public abstract ImmutableList<Result> transform(CldrValue cldrValue); 112 113 /** 114 * Transforms a CLDR value into a sequence of results (empty if the value was not matched by 115 * any rule). The dynamic variable function provides any "late bound" CLDR path variables to be 116 * resolved from CLDR data during processing (e.g "%D=//ldml/numbers/defaultNumberingSystem"). 117 * 118 * @param cldrValue the value to transform. 119 * @param varFn a function for resolving "late bound" variables. 120 * @return the transformed result(s). 121 */ transform(CldrValue cldrValue, DynamicVars varFn)122 public abstract ImmutableList<Result> transform(CldrValue cldrValue, DynamicVars varFn); 123 124 /** 125 * Returns a possibly empty sequence of fallback results for a given key. A fallback result for 126 * a key should be used only if it is not a fallback for any other result with that key; see 127 * also {@link Result#isFallbackFor(Result)}. 128 */ getFallbackResultsFor(RbPath key, DynamicVars varFn)129 public abstract ImmutableList<Result> getFallbackResultsFor(RbPath key, DynamicVars varFn); 130 } 131