Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(31)

Side by Side Diff: pkg/observe/lib/src/path_observer.dart

Issue 27618002: package:observe fix various api issues (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/observe/lib/src/observable_map.dart ('k') | pkg/observe/lib/transform.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of observe; 5 part of observe;
6 6
7 // This code is inspired by ChangeSummary: 7 // This code is inspired by ChangeSummary:
8 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 8 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
9 // ...which underlies MDV. Since we don't need the functionality of 9 // ...which underlies MDV. Since we don't need the functionality of
10 // ChangeSummary, we just implement what we need for data bindings. 10 // ChangeSummary, we just implement what we need for data bindings.
11 // This allows our implementation to be much simpler. 11 // This allows our implementation to be much simpler.
12 12
13 /** 13 /**
14 * A data-bound path starting from a view-model or model object, for example 14 * A data-bound path starting from a view-model or model object, for example
15 * `foo.bar.baz`. 15 * `foo.bar.baz`.
16 * 16 *
17 * When the [values] stream is being listened to, this will observe changes to 17 * When the [values] stream is being listened to, this will observe changes to
18 * the object and any intermediate object along the path, and send [values] 18 * the object and any intermediate object along the path, and send [values]
19 * accordingly. When all listeners are unregistered it will stop observing 19 * accordingly. When all listeners are unregistered it will stop observing
20 * the objects. 20 * the objects.
21 * 21 *
22 * This class is used to implement [Node.bind] and similar functionality. 22 * This class is used to implement [Node.bind] and similar functionality.
23 */ 23 */
24 class PathObserver extends ChangeNotifierBase { 24 class PathObserver extends ChangeNotifier {
25 /** The path string. */ 25 /** The path string. */
26 final String path; 26 final String path;
27 27
28 /** True if the path is valid, otherwise false. */ 28 /** True if the path is valid, otherwise false. */
29 final bool _isValid; 29 final bool _isValid;
30 30
31 final List<Object> _segments; 31 final List<Object> _segments;
32 List<Object> _values; 32 List<Object> _values;
33 List<StreamSubscription> _subs; 33 List<StreamSubscription> _subs;
34 34
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
112 } 112 }
113 113
114 // TODO(jmesserly): should we be caching these values if not observing? 114 // TODO(jmesserly): should we be caching these values if not observing?
115 void _updateValues() { 115 void _updateValues() {
116 for (int i = 0; i < _segments.length; i++) { 116 for (int i = 0; i < _segments.length; i++) {
117 _values[i + 1] = _getObjectProperty(_values[i], _segments[i]); 117 _values[i + 1] = _getObjectProperty(_values[i], _segments[i]);
118 } 118 }
119 } 119 }
120 120
121 void _updateObservedValues([int start = 0]) { 121 void _updateObservedValues([int start = 0]) {
122 bool changed = false; 122 var oldValue, newValue;
123 for (int i = start; i < _segments.length; i++) { 123 for (int i = start; i < _segments.length; i++) {
124 final newValue = _getObjectProperty(_values[i], _segments[i]); 124 oldValue = _values[i + 1];
125 if (identical(_values[i + 1], newValue)) { 125 newValue = _getObjectProperty(_values[i], _segments[i]);
126 if (identical(oldValue, newValue)) {
126 _observePath(start, i); 127 _observePath(start, i);
127 return; 128 return;
128 } 129 }
129 _values[i + 1] = newValue; 130 _values[i + 1] = newValue;
130 changed = true;
131 } 131 }
132 132
133 _observePath(start); 133 _observePath(start);
134 if (changed) { 134 notifyPropertyChange(#value, oldValue, newValue);
135 notifyChange(new PropertyChangeRecord(#value));
136 }
137 } 135 }
138 136
139 void _observePath([int start = 0, int end]) { 137 void _observePath([int start = 0, int end]) {
140 if (end == null) end = _segments.length; 138 if (end == null) end = _segments.length;
141 139
142 for (int i = start; i < end; i++) { 140 for (int i = start; i < end; i++) {
143 if (_subs[i] != null) _subs[i].cancel(); 141 if (_subs[i] != null) _subs[i].cancel();
144 _observeIndex(i); 142 _observeIndex(i);
145 } 143 }
146 } 144 }
147 145
148 void _observeIndex(int i) { 146 void _observeIndex(int i) {
149 final object = _values[i]; 147 final object = _values[i];
150 if (object is Observable) { 148 if (object is Observable) {
151 // TODO(jmesserly): rather than allocating a new closure for each 149 // TODO(jmesserly): rather than allocating a new closure for each
152 // property, we could try and have one for the entire path. In that case, 150 // property, we could try and have one for the entire path. In that case,
153 // we would lose information about which object changed (note: unless 151 // we would lose information about which object changed (note: unless
154 // PropertyChangeRecord is modified to includes the sender object), so 152 // PropertyChangeRecord is modified to includes the sender object), so
155 // we would need to re-evaluate the entire path. Need to evaluate perf. 153 // we would need to re-evaluate the entire path. Need to evaluate perf.
156 _subs[i] = object.changes.listen((List<ChangeRecord> records) { 154 _subs[i] = object.changes.listen((List<ChangeRecord> records) {
157 if (!identical(_values[i], object)) { 155 if (!identical(_values[i], object)) {
158 // Ignore this object if we're now tracking something else. 156 // Ignore this object if we're now tracking something else.
159 return; 157 return;
160 } 158 }
161 159
162 for (var record in records) { 160 for (var record in records) {
163 if (record.changes(_segments[i])) { 161 if (_changeRecordMatches(record, _segments[i])) {
164 _updateObservedValues(i); 162 _updateObservedValues(i);
165 return; 163 return;
166 } 164 }
167 } 165 }
168 }); 166 });
169 } 167 }
170 } 168 }
171 } 169 }
172 170
171 bool _changeRecordMatches(record, key) {
172 if (record is ListChangeRecord) {
173 return key is int && (record as ListChangeRecord).indexChanged(key);
174 }
175 if (record is PropertyChangeRecord) {
176 return (record as PropertyChangeRecord).name == key;
177 }
178 if (record is MapChangeRecord) {
179 if (key is Symbol) key = MirrorSystem.getName(key);
180 return (record as MapChangeRecord).key == key;
181 }
182 return false;
183 }
184
173 _getObjectProperty(object, property) { 185 _getObjectProperty(object, property) {
174 if (object == null) { 186 if (object == null) {
175 return null; 187 return null;
176 } 188 }
177 189
178 if (object is List && property is int) { 190 if (object is List && property is int) {
179 if (property >= 0 && property < object.length) { 191 if (property >= 0 && property < object.length) {
180 return object[property]; 192 return object[property];
181 } else { 193 } else {
182 return null; 194 return null;
183 } 195 }
184 } 196 }
185 197
186 if (property is Symbol) { 198 if (property is Symbol) {
187 var mirror = reflect(object); 199 var mirror = reflect(object);
188 try { 200 var result = _tryGetField(mirror, property);
189 return mirror.getField(property).reflectee; 201 if (result != null) return result.reflectee;
190 } catch (e) {}
191 } 202 }
192 203
193 if (object is Map) { 204 if (object is Map) {
205 if (property is Symbol) property = MirrorSystem.getName(property);
194 return object[property]; 206 return object[property];
195 } 207 }
196 208
197 return null; 209 return null;
198 } 210 }
199 211
200 bool _setObjectProperty(object, property, value) { 212 bool _setObjectProperty(object, property, value) {
201 if (object is List && property is int) { 213 if (object is List && property is int) {
202 if (property >= 0 && property < object.length) { 214 if (property >= 0 && property < object.length) {
203 object[property] = value; 215 object[property] = value;
204 return true; 216 return true;
205 } else { 217 } else {
206 return false; 218 return false;
207 } 219 }
208 } 220 }
209 221
210 if (property is Symbol) { 222 if (property is Symbol) {
211 var mirror = reflect(object); 223 var mirror = reflect(object);
212 try { 224 if (_trySetField(mirror, property, value)) return true;
213 mirror.setField(property, value);
214 return true;
215 } catch (e) {}
216 } 225 }
217 226
218 if (object is Map) { 227 if (object is Map) {
228 if (property is Symbol) property = MirrorSystem.getName(property);
219 object[property] = value; 229 object[property] = value;
220 return true; 230 return true;
221 } 231 }
222 232
223 return false; 233 return false;
224 } 234 }
225 235
236 InstanceMirror _tryGetField(InstanceMirror mirror, Symbol name) {
237 try {
238 return mirror.getField(name);
239 } on NoSuchMethodError catch (e) {
240 if (_hasMember(mirror, name, (m) =>
241 m is VariableMirror || m is MethodMirror && m.isGetter)) {
242 // The field/getter is there but threw a NoSuchMethod exception.
243 // This is a legitimate error in the code so rethrow.
244 rethrow;
245 }
246 // The field isn't there. PathObserver does not treat this as an error.
247 return null;
248 }
249 }
250
251 bool _trySetField(InstanceMirror mirror, Symbol name, Object value) {
252 try {
253 mirror.setField(name, value);
254 return true;
255 } on NoSuchMethodError catch (e) {
256 if (_hasMember(mirror, name, (m) => m is VariableMirror) ||
257 _hasMember(mirror, _setterName(name))) {
258 // The field/setter is there but threw a NoSuchMethod exception.
259 // This is a legitimate error in the code so rethrow.
260 rethrow;
261 }
262 // The field isn't there. PathObserver does not treat this as an error.
263 return false;
264 }
265 }
266
267 // TODO(jmesserly): workaround for:
268 // https://code.google.com/p/dart/issues/detail?id=10029
269 Symbol _setterName(Symbol getter) =>
270 new Symbol('${MirrorSystem.getName(getter)}=');
271
272 bool _hasMember(InstanceMirror mirror, Symbol name, [bool test(member)]) {
273 var type = mirror.type;
274 while (type != null) {
275 final member = type.members[name];
276 if (member != null && (test == null || test(member))) return true;
277
278 try {
279 type = type.superclass;
280 } on UnsupportedError catch (e) {
281 // TODO(jmesserly): dart2js throws this error when the type is not
282 // reflectable.
283 return false;
284 }
285 }
286 return false;
287 }
226 288
227 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 289 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
228 290
229 final _pathRegExp = () { 291 final _pathRegExp = () {
230 const identStart = '[\$_a-zA-Z]'; 292 const identStart = '[\$_a-zA-Z]';
231 const identPart = '[\$_a-zA-Z0-9]'; 293 const identPart = '[\$_a-zA-Z0-9]';
232 const ident = '$identStart+$identPart*'; 294 const ident = '$identStart+$identPart*';
233 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; 295 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)';
234 const identOrElementIndex = '(?:$ident|$elementIndex)'; 296 const identOrElementIndex = '(?:$ident|$elementIndex)';
235 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*'; 297 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*';
236 return new RegExp('^$path\$'); 298 return new RegExp('^$path\$');
237 }(); 299 }();
238 300
239 final _spacesRegExp = new RegExp(r'\s'); 301 final _spacesRegExp = new RegExp(r'\s');
240 302
241 bool _isPathValid(String s) { 303 bool _isPathValid(String s) {
242 s = s.replaceAll(_spacesRegExp, ''); 304 s = s.replaceAll(_spacesRegExp, '');
243 305
244 if (s == '') return true; 306 if (s == '') return true;
245 if (s[0] == '.') return false; 307 if (s[0] == '.') return false;
246 return _pathRegExp.hasMatch(s); 308 return _pathRegExp.hasMatch(s);
247 } 309 }
OLDNEW
« no previous file with comments | « pkg/observe/lib/src/observable_map.dart ('k') | pkg/observe/lib/transform.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698