1 package org.unicode.cldr.api; 2 3 import static com.google.common.base.Preconditions.checkNotNull; 4 import static com.google.common.base.Preconditions.checkState; 5 import static org.unicode.cldr.api.CldrData.PathOrder.NESTED_GROUPING; 6 7 import java.util.ArrayDeque; 8 import java.util.Deque; 9 import java.util.function.BiConsumer; 10 import java.util.function.Consumer; 11 import org.unicode.cldr.api.CldrData.PathOrder; 12 import org.unicode.cldr.api.CldrData.PrefixVisitor; 13 import org.unicode.cldr.api.CldrData.PrefixVisitor.Context; 14 import org.unicode.cldr.api.CldrData.ValueVisitor; 15 16 /** 17 * Utility class for reconstructing nested path visitation from a sequence of path/value pairs. See 18 * {@link PrefixVisitor} for more information. 19 */ 20 final class PrefixVisitorHost { 21 /** 22 * Accepts a prefix visitor over a nested sequence of prefix paths derived from the given data 23 * instance. This method synthesizes a sequence of start/end events for all the sub-trees 24 * implied by the sequence of CLDR paths produced by the data supplier. 25 * 26 * <p>For example, given the sequence: 27 * 28 * <pre>{@code 29 * //ldml/foo/bar/first 30 * //ldml/foo/bar/second 31 * //ldml/foo/bar/third 32 * //ldml/foo/baz/first 33 * //ldml/foo/baz/second 34 * //ldml/quux 35 * }</pre> 36 * 37 * the following start, end and value visitation events will be derived: 38 * 39 * <pre>{@code 40 * start: //ldml 41 * start: //ldml/foo 42 * start: //ldml/foo/bar 43 * value: //ldml/foo/bar/first 44 * value: //ldml/foo/bar/second 45 * value: //ldml/foo/bar/third 46 * end: //ldml/foo/bar 47 * start: //ldml/foo/baz 48 * value: //ldml/foo/baz/first 49 * value: //ldml/foo/baz/second 50 * end: //ldml/foo/baz 51 * end: //ldml/foo 52 * value: //ldml/quux 53 * end: //ldml 54 * }</pre> 55 * 56 * <p>Note that deriving the proper sequence of start/end events can only occur if the data is 57 * provided in at least {@link PathOrder#NESTED_GROUPING NESTED_GROUPING} order. If a lower path 58 * order (e.g. {@link PathOrder#ARBITRARY ARBITRARY}) is given then {@code NESTED_GROUPING} will 59 * be used. 60 */ accept( BiConsumer<PathOrder, ValueVisitor> acceptFn, PathOrder order, PrefixVisitor v)61 static void accept( 62 BiConsumer<PathOrder, ValueVisitor> acceptFn, PathOrder order, PrefixVisitor v) { 63 PrefixVisitorHost host = new PrefixVisitorHost(v); 64 if (order.ordinal() < NESTED_GROUPING.ordinal()) { 65 order = NESTED_GROUPING; 66 } 67 acceptFn.accept(order, host.visitor); 68 host.endVisitation(); 69 } 70 71 /** 72 * Represents the root of a sub hierarchy visitation rooted at some path prefix. VisitorState 73 * instances are kept in a stack; they are added when a new visitor is installed to begin a sub 74 * hierarchy visitation and removed automatically once the visitation is complete. 75 */ 76 @SuppressWarnings("unused") // For unused arguments in no-op default methods. 77 private abstract static class VisitorState implements PrefixVisitor { 78 /** Creates a visitor state from the given visitor for the specified leaf value. */ of( T visitor, Consumer<T> doneHandler, CldrPath prefix)79 static <T extends ValueVisitor> VisitorState of( 80 T visitor, Consumer<T> doneHandler, CldrPath prefix) { 81 return new VisitorState(prefix, () -> doneHandler.accept(visitor)) { 82 @Override 83 public void visitValue(CldrValue value) { 84 visitor.visit(value); 85 } 86 }; 87 } 88 89 /** Creates a visitor state from the given visitor rooted at the specified path prefix. */ of( T visitor, Consumer<T> doneHandler, CldrPath prefix)90 static <T extends PrefixVisitor> VisitorState of( 91 T visitor, Consumer<T> doneHandler, CldrPath prefix) { 92 return new VisitorState(prefix, () -> doneHandler.accept(visitor)) { 93 @Override 94 public void visitPrefixStart(CldrPath prefix, Context ctx) { 95 visitor.visitPrefixStart(prefix, ctx); 96 } 97 98 @Override 99 public void visitPrefixEnd(CldrPath prefix) { 100 visitor.visitPrefixEnd(prefix); 101 } 102 103 @Override 104 public void visitValue(CldrValue value) { 105 visitor.visitValue(value); 106 } 107 }; 108 } 109 110 // The root of the sub hierarchy visitation. 111 /* @Nullable */ private final CldrPath prefix; 112 private final Runnable doneCallback; 113 114 private VisitorState(CldrPath prefix, Runnable doneCallback) { 115 this.prefix = prefix; 116 this.doneCallback = doneCallback; 117 } 118 } 119 120 // Stack of currently installed visitor. 121 private final Deque<VisitorState> visitorStack = new ArrayDeque<>(); 122 /* @Nullable */ private CldrPath lastValuePath = null; 123 124 // Visits a single value (with its path) and synthesizes prefix start/end calls according 125 // to the state of the visitor stack. 126 // This is a private field to avoid anyone accidentally calling the visit method directly. 127 private final ValueVisitor visitor = 128 value -> { 129 CldrPath path = value.getPath(); 130 int commonLength = 0; 131 if (lastValuePath != null) { 132 commonLength = CldrPath.getCommonPrefixLength(lastValuePath, path); 133 checkState( 134 commonLength <= lastValuePath.getLength(), 135 "unexpected child path encountered: %s is child of %s", 136 path, 137 lastValuePath); 138 handleLastPath(commonLength); 139 } 140 // ... then down to the new path (which cannot be a parent of the old path either). 141 checkState( 142 commonLength <= path.getLength(), 143 "unexpected parent path encountered: %s is parent of %s", 144 path, 145 lastValuePath); 146 recursiveStartVisit(path.getParent(), commonLength, new PrefixContext()); 147 // This is a no-op if the head of the stack is a prefix visitor. 148 visitorStack.peek().visitValue(value); 149 lastValuePath = path; 150 }; 151 152 private PrefixVisitorHost(PrefixVisitor visitor) { 153 this.visitorStack.push(VisitorState.of(visitor, v -> {}, null)); 154 } 155 156 // Called after visitation is complete to close out the last visited value path. 157 private void endVisitation() { 158 if (lastValuePath != null) { 159 handleLastPath(0); 160 } 161 } 162 163 // Recursively visits new prefix path elements (from top-to-bottom) for a new sub hierarchy. 164 private void recursiveStartVisit( 165 /* @Nullable */ CldrPath prefix, int commonLength, PrefixContext ctx) { 166 if (prefix != null && prefix.getLength() > commonLength) { 167 recursiveStartVisit(prefix.getParent(), commonLength, ctx); 168 // Get the current visitor here (it could have been modified by the call above). 169 // This is a no-op if the head of the stack is a value visitor. 170 visitorStack.peek().visitPrefixStart(prefix, ctx.setPrefix(prefix)); 171 } 172 } 173 174 // Go up from the previous path to the common length (we _have_ already visited the leaf 175 // node of the previous path and we do not allow the new path to be a sub-path) ... 176 private void handleLastPath(int length) { 177 for (CldrPath prefix = lastValuePath.getParent(); 178 prefix != null && prefix.getLength() > length; 179 prefix = prefix.getParent()) { 180 // Get the current visitor here (it could have been modified by the last iteration). 181 // 182 // Note: e.prefix can be null for the top-most entry in the stack, but that's fine 183 // since it will never match "prefix" and we never want to remove it anyway. 184 VisitorState e = visitorStack.peek(); 185 if (prefix.equals(e.prefix)) { 186 e.doneCallback.run(); 187 visitorStack.pop(); 188 e = visitorStack.peek(); 189 } 190 // This is a no-op if the head of the stack is a value visitor. 191 e.visitPrefixEnd(prefix); 192 } 193 } 194 195 /** 196 * Implements a reusable context which captures the current prefix being processed. This is used 197 * if a visitor wants to install a sub-visitor at a particular point during visitation. 198 */ 199 private final class PrefixContext implements Context { 200 // Only null until first use. 201 private CldrPath prefix = null; 202 203 // Must be called immediately prior to visiting a prefix visitor. 204 private PrefixContext setPrefix(CldrPath prefix) { 205 this.prefix = checkNotNull(prefix); 206 return this; 207 } 208 209 @Override 210 public <T extends PrefixVisitor> void install(T visitor, Consumer<T> doneHandler) { 211 visitorStack.push(VisitorState.of(visitor, doneHandler, prefix)); 212 } 213 214 @Override 215 public <T extends ValueVisitor> void install(T visitor, Consumer<T> doneHandler) { 216 visitorStack.push(VisitorState.of(visitor, doneHandler, prefix)); 217 } 218 } 219 } 220