| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 dart.core; | 5 part of dart.core; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * A parsed URI, such as a URL. | 8 * A parsed URI, such as a URL. |
| 9 * | 9 * |
| 10 * **See also:** | 10 * **See also:** |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 List<String> _pathSegments; | 111 List<String> _pathSegments; |
| 112 | 112 |
| 113 /** | 113 /** |
| 114 * Cache the computed return value of [queryParameters]. | 114 * Cache the computed return value of [queryParameters]. |
| 115 */ | 115 */ |
| 116 Map<String, String> _queryParameters; | 116 Map<String, String> _queryParameters; |
| 117 | 117 |
| 118 /** | 118 /** |
| 119 * Creates a new URI object by parsing a URI string. | 119 * Creates a new URI object by parsing a URI string. |
| 120 */ | 120 */ |
| 121 static Uri parse(String uri) => new Uri._fromMatch(_splitRe.firstMatch(uri)); | 121 static Uri parse(String uri) { |
| 122 | 122 // This parsing will not validate percent-encoding, IPv6, etc. When done |
| 123 Uri._fromMatch(Match m) : | 123 // it will call `new Uri(...)` which will perform these validations. |
| 124 this(scheme: _makeScheme(_emptyIfNull(m[_COMPONENT_SCHEME])), | 124 // This is purely splitting up the URI string into components. |
| 125 userInfo: _emptyIfNull(m[_COMPONENT_USER_INFO]), | 125 // |
| 126 host: _eitherOf( | 126 // Important parts of the RFC 3986 used here: |
| 127 m[_COMPONENT_HOST], m[_COMPONENT_HOST_IPV6]), | 127 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] |
| 128 port: _parseIntOrZero(m[_COMPONENT_PORT]), | 128 // |
| 129 path: _emptyIfNull(m[_COMPONENT_PATH]), | 129 // hier-part = "//" authority path-abempty |
| 130 query: _emptyIfNull(m[_COMPONENT_QUERY_DATA]), | 130 // / path-absolute |
| 131 fragment: _emptyIfNull(m[_COMPONENT_FRAGMENT])); | 131 // / path-rootless |
| 132 // / path-empty |
| 133 // |
| 134 // URI-reference = URI / relative-ref |
| 135 // |
| 136 // absolute-URI = scheme ":" hier-part [ "?" query ] |
| 137 // |
| 138 // relative-ref = relative-part [ "?" query ] [ "#" fragment ] |
| 139 // |
| 140 // relative-part = "//" authority path-abempty |
| 141 // / path-absolute |
| 142 // / path-noscheme |
| 143 // / path-empty |
| 144 // |
| 145 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
| 146 // |
| 147 // authority = [ userinfo "@" ] host [ ":" port ] |
| 148 // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) |
| 149 // host = IP-literal / IPv4address / reg-name |
| 150 // port = *DIGIT |
| 151 // reg-name = *( unreserved / pct-encoded / sub-delims ) |
| 152 // |
| 153 // path = path-abempty ; begins with "/" or is empty |
| 154 // / path-absolute ; begins with "/" but not "//" |
| 155 // / path-noscheme ; begins with a non-colon segment |
| 156 // / path-rootless ; begins with a segment |
| 157 // / path-empty ; zero characters |
| 158 // |
| 159 // path-abempty = *( "/" segment ) |
| 160 // path-absolute = "/" [ segment-nz *( "/" segment ) ] |
| 161 // path-noscheme = segment-nz-nc *( "/" segment ) |
| 162 // path-rootless = segment-nz *( "/" segment ) |
| 163 // path-empty = 0<pchar> |
| 164 // |
| 165 // segment = *pchar |
| 166 // segment-nz = 1*pchar |
| 167 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) |
| 168 // ; non-zero-length segment without any colon ":" |
| 169 // |
| 170 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
| 171 // |
| 172 // query = *( pchar / "/" / "?" ) |
| 173 // |
| 174 // fragment = *( pchar / "/" / "?" ) |
| 175 bool isRegName(int ch) { |
| 176 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
| 177 } |
| 178 |
| 179 int ipV6Address(List<int> codeUnits, int index) { |
| 180 // IPv6. Skip to ']'. |
| 181 index = codeUnits.indexOf(_RIGHT_BRACKET, index); |
| 182 if (index == -1) { |
| 183 throw new FormatException("Bad end of IPv6 host"); |
| 184 } |
| 185 return index + 1; |
| 186 } |
| 187 |
| 188 List<int> codeUnits = uri.codeUnits; |
| 189 int length = codeUnits.length; |
| 190 int index = 0; |
| 191 |
| 192 int schemeEndIndex = 0; |
| 193 |
| 194 if (length == 0) { |
| 195 return new Uri(); |
| 196 } |
| 197 |
| 198 if (codeUnits[0] != _SLASH) { |
| 199 // Can be scheme. |
| 200 while (index < length) { |
| 201 // Look for ':'. If found, continue from the post of ':'. If not (end |
| 202 // reached or invalid scheme char found) back up one char, and continue |
| 203 // to path. |
| 204 // Note that scheme-chars is contained in path-chars. |
| 205 int codeUnit = codeUnits[index++]; |
| 206 if (!_isSchemeCharacter(codeUnit)) { |
| 207 if (codeUnit == _COLON) { |
| 208 schemeEndIndex = index; |
| 209 } else { |
| 210 // Back up one char, since we met an invalid scheme char. |
| 211 index--; |
| 212 } |
| 213 break; |
| 214 } |
| 215 } |
| 216 } |
| 217 |
| 218 int userInfoEndIndex = -1; |
| 219 int portIndex = -1; |
| 220 int authorityEndIndex = schemeEndIndex; |
| 221 // If we see '//', there must be an authority. |
| 222 if (authorityEndIndex == index && |
| 223 authorityEndIndex + 1 < length && |
| 224 codeUnits[authorityEndIndex] == _SLASH && |
| 225 codeUnits[authorityEndIndex + 1] == _SLASH) { |
| 226 // Skip '//'. |
| 227 authorityEndIndex += 2; |
| 228 // It can both be host and userInfo. |
| 229 while (authorityEndIndex < length) { |
| 230 int codeUnit = codeUnits[authorityEndIndex++]; |
| 231 if (!isRegName(codeUnit)) { |
| 232 if (codeUnit == _LEFT_BRACKET) { |
| 233 authorityEndIndex = ipV6Address(codeUnits, authorityEndIndex); |
| 234 } else if (portIndex == -1 && codeUnit == _COLON) { |
| 235 // First time ':'. |
| 236 portIndex = authorityEndIndex; |
| 237 } else if (codeUnit == _AT_SIGN || codeUnit == _COLON) { |
| 238 // Second time ':' or first '@'. Must be userInfo. |
| 239 userInfoEndIndex = codeUnits.indexOf('@'.codeUnitAt(0), |
| 240 authorityEndIndex - 1); |
| 241 // Not found. Must be path then. |
| 242 if (userInfoEndIndex == -1) { |
| 243 authorityEndIndex = index; |
| 244 break; |
| 245 } |
| 246 portIndex = -1; |
| 247 authorityEndIndex = userInfoEndIndex + 1; |
| 248 // Now it can only be host:port. |
| 249 while (authorityEndIndex < length) { |
| 250 int codeUnit = codeUnits[authorityEndIndex++]; |
| 251 if (!isRegName(codeUnit)) { |
| 252 if (codeUnit == _LEFT_BRACKET) { |
| 253 authorityEndIndex = ipV6Address(codeUnits, authorityEndIndex); |
| 254 } else if (codeUnit == _COLON) { |
| 255 if (portIndex != -1) { |
| 256 throw new FormatException("Double port in host"); |
| 257 } |
| 258 portIndex = authorityEndIndex; |
| 259 } else { |
| 260 authorityEndIndex--; |
| 261 break; |
| 262 } |
| 263 } |
| 264 } |
| 265 break; |
| 266 } else { |
| 267 authorityEndIndex--; |
| 268 break; |
| 269 } |
| 270 } |
| 271 } |
| 272 } else { |
| 273 authorityEndIndex = schemeEndIndex; |
| 274 } |
| 275 |
| 276 // At path now. |
| 277 int pathEndIndex = authorityEndIndex; |
| 278 while (pathEndIndex < length) { |
| 279 int codeUnit = codeUnits[pathEndIndex++]; |
| 280 if (codeUnit == _QUESTION || codeUnit == _NUMBER_SIGN) { |
| 281 pathEndIndex--; |
| 282 break; |
| 283 } |
| 284 } |
| 285 |
| 286 // Maybe query. |
| 287 int queryEndIndex = pathEndIndex; |
| 288 if (queryEndIndex < length && codeUnits[queryEndIndex] == _QUESTION) { |
| 289 while (queryEndIndex < length) { |
| 290 int codeUnit = codeUnits[queryEndIndex++]; |
| 291 if (codeUnit == _NUMBER_SIGN) { |
| 292 queryEndIndex--; |
| 293 break; |
| 294 } |
| 295 } |
| 296 } |
| 297 |
| 298 var scheme = null; |
| 299 if (schemeEndIndex > 0) { |
| 300 scheme = uri.substring(0, schemeEndIndex - 1); |
| 301 } |
| 302 |
| 303 var host = ""; |
| 304 var userInfo = ""; |
| 305 var port = 0; |
| 306 if (schemeEndIndex != authorityEndIndex) { |
| 307 int startIndex = schemeEndIndex + 2; |
| 308 if (userInfoEndIndex > 0) { |
| 309 userInfo = uri.substring(startIndex, userInfoEndIndex); |
| 310 startIndex = userInfoEndIndex + 1; |
| 311 } |
| 312 if (portIndex > 0) { |
| 313 var portStr = uri.substring(portIndex, authorityEndIndex); |
| 314 try { |
| 315 port = int.parse(portStr); |
| 316 } catch (_) { |
| 317 throw new FormatException("Invalid port: '$portStr'"); |
| 318 } |
| 319 host = uri.substring(startIndex, portIndex - 1); |
| 320 } else { |
| 321 host = uri.substring(startIndex, authorityEndIndex); |
| 322 } |
| 323 } |
| 324 |
| 325 var path = uri.substring(authorityEndIndex, pathEndIndex); |
| 326 var query = ""; |
| 327 if (pathEndIndex < queryEndIndex) { |
| 328 query = uri.substring(pathEndIndex + 1, queryEndIndex); |
| 329 } |
| 330 var fragment = ""; |
| 331 // If queryEndIndex is not at end (length), there is a fragment. |
| 332 if (queryEndIndex < length) { |
| 333 fragment = uri.substring(queryEndIndex + 1, length); |
| 334 } |
| 335 |
| 336 return new Uri(scheme: scheme, |
| 337 userInfo: userInfo, |
| 338 host: host, |
| 339 port: port, |
| 340 path: path, |
| 341 query: query, |
| 342 fragment: fragment); |
| 343 } |
| 132 | 344 |
| 133 /** | 345 /** |
| 134 * Creates a new URI from its components. | 346 * Creates a new URI from its components. |
| 135 * | 347 * |
| 136 * Each component is set through a named argument. Any number of | 348 * Each component is set through a named argument. Any number of |
| 137 * components can be provided. The default value for the components | 349 * components can be provided. The default value for the components |
| 138 * not provided is the empry string, except for [port] which has a | 350 * not provided is the empry string, except for [port] which has a |
| 139 * default value of 0. The [path] and [query] components can be set | 351 * default value of 0. The [path] and [query] components can be set |
| 140 * using two different named arguments. | 352 * using two different named arguments. |
| 141 * | 353 * |
| (...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 538 } | 750 } |
| 539 return host; | 751 return host; |
| 540 } | 752 } |
| 541 | 753 |
| 542 static String _makeScheme(String scheme) { | 754 static String _makeScheme(String scheme) { |
| 543 bool isSchemeLowerCharacter(int ch) { | 755 bool isSchemeLowerCharacter(int ch) { |
| 544 return ch < 128 && | 756 return ch < 128 && |
| 545 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 757 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
| 546 } | 758 } |
| 547 | 759 |
| 548 bool isSchemeCharacter(int ch) { | |
| 549 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | |
| 550 } | |
| 551 | |
| 552 if (scheme == null) return ""; | 760 if (scheme == null) return ""; |
| 553 bool allLowercase = true; | 761 bool allLowercase = true; |
| 554 int length = scheme.length; | 762 int length = scheme.length; |
| 555 for (int i = 0; i < length; i++) { | 763 for (int i = 0; i < length; i++) { |
| 556 int codeUnit = scheme.codeUnitAt(i); | 764 int codeUnit = scheme.codeUnitAt(i); |
| 557 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) { | 765 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) { |
| 558 // First code unit must be an alphabetic character. | 766 // First code unit must be an alphabetic character. |
| 559 throw new ArgumentError('Illegal scheme: $scheme'); | 767 throw new ArgumentError('Illegal scheme: $scheme'); |
| 560 } | 768 } |
| 561 if (!isSchemeLowerCharacter(codeUnit)) { | 769 if (!isSchemeLowerCharacter(codeUnit)) { |
| 562 if (isSchemeCharacter(codeUnit)) { | 770 if (_isSchemeCharacter(codeUnit)) { |
| 563 allLowercase = false; | 771 allLowercase = false; |
| 564 } else { | 772 } else { |
| 565 throw new ArgumentError('Illegal scheme: $scheme'); | 773 throw new ArgumentError('Illegal scheme: $scheme'); |
| 566 } | 774 } |
| 567 } | 775 } |
| 568 } | 776 } |
| 569 | 777 |
| 570 return allLowercase ? scheme : scheme.toLowerCase(); | 778 return allLowercase ? scheme : scheme.toLowerCase(); |
| 571 } | 779 } |
| 572 | 780 |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 712 index++; | 920 index++; |
| 713 } | 921 } |
| 714 } | 922 } |
| 715 if (result != null && prevIndex != index) fillResult(); | 923 if (result != null && prevIndex != index) fillResult(); |
| 716 assert(index == length); | 924 assert(index == length); |
| 717 | 925 |
| 718 if (result == null) return component; | 926 if (result == null) return component; |
| 719 return result.toString(); | 927 return result.toString(); |
| 720 } | 928 } |
| 721 | 929 |
| 722 static String _emptyIfNull(String val) => val != null ? val : ''; | 930 static bool _isSchemeCharacter(int ch) { |
| 723 | 931 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
| 724 static int _parseIntOrZero(String val) { | |
| 725 if (val != null && val != '') { | |
| 726 return int.parse(val); | |
| 727 } else { | |
| 728 return 0; | |
| 729 } | |
| 730 } | 932 } |
| 731 | 933 |
| 732 static String _eitherOf(String val1, String val2) { | |
| 733 if (val1 != null) return val1; | |
| 734 if (val2 != null) return val2; | |
| 735 return ''; | |
| 736 } | |
| 737 | |
| 738 // NOTE: This code was ported from: closure-library/closure/goog/uri/utils.js | |
| 739 static final RegExp _splitRe = new RegExp( | |
| 740 '^' | |
| 741 '(?:' | |
| 742 '([^:/?#]+)' // scheme - ignore special characters | |
| 743 // used by other URL parts such as :, | |
| 744 // ?, /, #, and . | |
| 745 ':)?' | |
| 746 '(?://' | |
| 747 '(?:([^/?#]*)@)?' // userInfo | |
| 748 '(?:' | |
| 749 r'([\w\d\-\u0100-\uffff.%]*)' | |
| 750 // host - restrict to letters, | |
| 751 // digits, dashes, dots, percent | |
| 752 // escapes, and unicode characters. | |
| 753 '|' | |
| 754 // TODO(ajohnsen): Only allow a max number of parts? | |
| 755 r'\[([A-Fa-f0-9:.]*)\])' | |
| 756 // IPv6 host - restrict to hex, | |
| 757 // dot and colon. | |
| 758 '(?::([0-9]+))?' // port | |
| 759 ')?' | |
| 760 r'([^?#[]+)?' // path | |
| 761 r'(?:\?([^#]*))?' // query | |
| 762 '(?:#(.*))?' // fragment | |
| 763 r'$'); | |
| 764 | |
| 765 static const _COMPONENT_SCHEME = 1; | |
| 766 static const _COMPONENT_USER_INFO = 2; | |
| 767 static const _COMPONENT_HOST = 3; | |
| 768 static const _COMPONENT_HOST_IPV6 = 4; | |
| 769 static const _COMPONENT_PORT = 5; | |
| 770 static const _COMPONENT_PATH = 6; | |
| 771 static const _COMPONENT_QUERY_DATA = 7; | |
| 772 static const _COMPONENT_FRAGMENT = 8; | |
| 773 | 934 |
| 774 /** | 935 /** |
| 775 * Returns whether the URI is absolute. | 936 * Returns whether the URI is absolute. |
| 776 */ | 937 */ |
| 777 bool get isAbsolute => scheme != "" && fragment == ""; | 938 bool get isAbsolute => scheme != "" && fragment == ""; |
| 778 | 939 |
| 779 String _merge(String base, String reference) { | 940 String _merge(String base, String reference) { |
| 780 if (base == "") return "/$reference"; | 941 if (base == "") return "/$reference"; |
| 781 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference"; | 942 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference"; |
| 782 } | 943 } |
| (...skipping 579 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1362 } else { | 1523 } else { |
| 1363 return [(value >> 8) & 0xFF, value & 0xFF]; | 1524 return [(value >> 8) & 0xFF, value & 0xFF]; |
| 1364 } | 1525 } |
| 1365 }) | 1526 }) |
| 1366 .toList(); | 1527 .toList(); |
| 1367 } | 1528 } |
| 1368 | 1529 |
| 1369 // Frequently used character codes. | 1530 // Frequently used character codes. |
| 1370 static const int _SPACE = 0x20; | 1531 static const int _SPACE = 0x20; |
| 1371 static const int _DOUBLE_QUOTE = 0x22; | 1532 static const int _DOUBLE_QUOTE = 0x22; |
| 1533 static const int _NUMBER_SIGN = 0x23; |
| 1372 static const int _PERCENT = 0x25; | 1534 static const int _PERCENT = 0x25; |
| 1373 static const int _ASTERISK = 0x2A; | 1535 static const int _ASTERISK = 0x2A; |
| 1374 static const int _PLUS = 0x2B; | 1536 static const int _PLUS = 0x2B; |
| 1375 static const int _SLASH = 0x2F; | 1537 static const int _SLASH = 0x2F; |
| 1376 static const int _ZERO = 0x30; | 1538 static const int _ZERO = 0x30; |
| 1377 static const int _NINE = 0x39; | 1539 static const int _NINE = 0x39; |
| 1378 static const int _COLON = 0x3A; | 1540 static const int _COLON = 0x3A; |
| 1379 static const int _LESS = 0x3C; | 1541 static const int _LESS = 0x3C; |
| 1380 static const int _GREATER = 0x3E; | 1542 static const int _GREATER = 0x3E; |
| 1381 static const int _QUESTION = 0x3F; | 1543 static const int _QUESTION = 0x3F; |
| (...skipping 236 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1618 0x2bff, // 0x30 - 0x3f 1111111111010100 | 1780 0x2bff, // 0x30 - 0x3f 1111111111010100 |
| 1619 // ABCDEFGHIJKLMNO | 1781 // ABCDEFGHIJKLMNO |
| 1620 0xfffe, // 0x40 - 0x4f 0111111111111111 | 1782 0xfffe, // 0x40 - 0x4f 0111111111111111 |
| 1621 // PQRSTUVWXYZ _ | 1783 // PQRSTUVWXYZ _ |
| 1622 0x87ff, // 0x50 - 0x5f 1111111111100001 | 1784 0x87ff, // 0x50 - 0x5f 1111111111100001 |
| 1623 // abcdefghijklmno | 1785 // abcdefghijklmno |
| 1624 0xfffe, // 0x60 - 0x6f 0111111111111111 | 1786 0xfffe, // 0x60 - 0x6f 0111111111111111 |
| 1625 // pqrstuvwxyz ~ | 1787 // pqrstuvwxyz ~ |
| 1626 0x47ff]; // 0x70 - 0x7f 1111111111100010 | 1788 0x47ff]; // 0x70 - 0x7f 1111111111100010 |
| 1627 | 1789 |
| 1790 // Characters allowed in the reg-name as of RFC 3986. |
| 1791 // RFC 3986 Apendix A |
| 1792 // reg-name = *( unreserved / pct-encoded / sub-delims ) |
| 1793 static const _regNameTable = const [ |
| 1794 // LSB MSB |
| 1795 // | | |
| 1796 0x0000, // 0x00 - 0x0f 0000000000000000 |
| 1797 0x0000, // 0x10 - 0x1f 0000000000000000 |
| 1798 // ! $%&'()*+,-. |
| 1799 0x7ff2, // 0x20 - 0x2f 0100111111111110 |
| 1800 // 0123456789 ; = |
| 1801 0x2bff, // 0x30 - 0x3f 1111111111010100 |
| 1802 // ABCDEFGHIJKLMNO |
| 1803 0xfffe, // 0x40 - 0x4f 0111111111111111 |
| 1804 // PQRSTUVWXYZ _ |
| 1805 0x87ff, // 0x50 - 0x5f 1111111111100001 |
| 1806 // abcdefghijklmno |
| 1807 0xfffe, // 0x60 - 0x6f 0111111111111111 |
| 1808 // pqrstuvwxyz ~ |
| 1809 0x47ff]; // 0x70 - 0x7f 1111111111100010 |
| 1810 |
| 1628 // Characters allowed in the path as of RFC 3986. | 1811 // Characters allowed in the path as of RFC 3986. |
| 1629 // RFC 3986 section 3.3. | 1812 // RFC 3986 section 3.3. |
| 1630 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" | 1813 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
| 1631 static const _pathCharTable = const [ | 1814 static const _pathCharTable = const [ |
| 1632 // LSB MSB | 1815 // LSB MSB |
| 1633 // | | | 1816 // | | |
| 1634 0x0000, // 0x00 - 0x0f 0000000000000000 | 1817 0x0000, // 0x00 - 0x0f 0000000000000000 |
| 1635 0x0000, // 0x10 - 0x1f 0000000000000000 | 1818 0x0000, // 0x10 - 0x1f 0000000000000000 |
| 1636 // ! $ &'()*+,-. | 1819 // ! $ &'()*+,-. |
| 1637 0x7fd2, // 0x20 - 0x2f 0100101111111110 | 1820 0x7fd2, // 0x20 - 0x2f 0100101111111110 |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1690 void clear() { | 1873 void clear() { |
| 1691 throw new UnsupportedError("Cannot modify an unmodifiable map"); | 1874 throw new UnsupportedError("Cannot modify an unmodifiable map"); |
| 1692 } | 1875 } |
| 1693 void forEach(void f(K key, V value)) => _map.forEach(f); | 1876 void forEach(void f(K key, V value)) => _map.forEach(f); |
| 1694 Iterable<K> get keys => _map.keys; | 1877 Iterable<K> get keys => _map.keys; |
| 1695 Iterable<V> get values => _map.values; | 1878 Iterable<V> get values => _map.values; |
| 1696 int get length => _map.length; | 1879 int get length => _map.length; |
| 1697 bool get isEmpty => _map.isEmpty; | 1880 bool get isEmpty => _map.isEmpty; |
| 1698 bool get isNotEmpty => _map.isNotEmpty; | 1881 bool get isNotEmpty => _map.isNotEmpty; |
| 1699 } | 1882 } |
| OLD | NEW |