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