1 /******************************************************************************* 2 * Copyright (c) 2009, 2017 Cloudsmith Inc. and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Cloudsmith Inc. - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.equinox.internal.p2.metadata; 15 16 import java.io.Serializable; 17 import java.util.*; 18 import org.eclipse.equinox.internal.p2.metadata.EnumDefinition.EnumSegment; 19 import org.eclipse.equinox.internal.p2.metadata.VersionFormat.TreeInfo; 20 import org.eclipse.equinox.p2.metadata.VersionFormatException; 21 import org.eclipse.osgi.util.NLS; 22 23 /** 24 * This is the Omni Version Format parser. It will parse a version format in string form 25 * into a group of {@link VersionFormatParser.Fragment} elements. That group, wrapped in a 26 * {@link VersionFormat}, becomes the parser for versions corresponding to the format. 27 * 28 * The class is not intended to included in a public API. Instead VersionFormats should 29 * be created using {@link VersionFormat#parse(String)} 30 * 31 */ 32 class VersionFormatParser { 33 34 private static class EnumInstruction { 35 private final EnumDefinition definition; 36 private final boolean caseSensitive; 37 private final boolean optional; 38 private final boolean begins; 39 EnumInstruction(EnumDefinition definition, boolean caseSensitive, boolean optional, boolean begins)40 EnumInstruction(EnumDefinition definition, boolean caseSensitive, boolean optional, boolean begins) { 41 this.definition = definition; 42 this.caseSensitive = caseSensitive; 43 this.optional = optional; 44 this.begins = begins; 45 } 46 getEnumSegment(RangeFragment fragment, String version, int[] posHolder, int maxPos)47 EnumSegment getEnumSegment(RangeFragment fragment, String version, int[] posHolder, int maxPos) { 48 int pos = posHolder[0]; 49 int len = maxPos - pos; 50 int minLen = definition.getShortestLength(); 51 if (minLen > len) 52 return null; 53 54 int maxLen = definition.getLongestLength(); 55 if (maxLen < len) 56 len = maxLen; 57 58 ++len; 59 while (--len >= minLen) { 60 int last = pos + len; 61 if (!begins && last < maxPos) { 62 char c = version.charAt(last); 63 if (VersionParser.isLetter(c) && fragment.isAllowed(c)) { 64 // We are not allowed to truncate at this point 65 continue; 66 } 67 } 68 String identifier = version.substring(pos, last); 69 if (!caseSensitive) 70 identifier = identifier.toLowerCase(); 71 int ordinal = definition.getOrdinal(identifier); 72 if (ordinal >= 0) { 73 posHolder[0] = pos + len; 74 return definition.getSegment(ordinal); 75 } 76 } 77 return null; 78 } 79 toString(StringBuffer bld)80 void toString(StringBuffer bld) { 81 bld.append('='); 82 definition.toString(bld); 83 if (begins) 84 bld.append('b'); 85 if (!caseSensitive) 86 bld.append('i'); 87 if (optional) 88 bld.append('?'); 89 bld.append(';'); 90 } 91 isOptional()92 public boolean isOptional() { 93 return optional; 94 } 95 } 96 97 static class Instructions { 98 char[] characters = null; 99 Comparable<?> defaultValue = null; 100 char oppositeTranslationChar = 0; 101 int oppositeTranslationRepeat = 0; 102 boolean ignore = false; 103 boolean inverted = false; 104 Comparable<?> padValue = null; 105 int rangeMax = Integer.MAX_VALUE; 106 int rangeMin = 0; 107 EnumInstruction enumInstruction = null; 108 } 109 110 static final Qualifier EXACT_ONE_QUALIFIER = new Qualifier(1, 1); 111 112 static final Qualifier ONE_OR_MANY_QUALIFIER = new Qualifier(1, Integer.MAX_VALUE); 113 114 static final Qualifier ZERO_OR_MANY_QUALIFIER = new Qualifier(0, Integer.MAX_VALUE); 115 116 static final Qualifier ZERO_OR_ONE_QUALIFIER = new Qualifier(0, 1); 117 118 /** 119 * Represents one fragment of a format (i.e. auto, number, string, delimiter, etc.) 120 */ 121 static abstract class Fragment implements Serializable { 122 private static final long serialVersionUID = 4109185333058622681L; 123 124 private final Qualifier qualifier; 125 Fragment(Qualifier qualifier)126 Fragment(Qualifier qualifier) { 127 this.qualifier = qualifier; 128 } 129 130 @Override equals(Object f)131 public final boolean equals(Object f) { 132 return f == this || getClass().equals(f.getClass()) && qualifier.equals(((Fragment) f).qualifier); 133 } 134 135 @Override hashCode()136 public final int hashCode() { 137 return 11 * qualifier.hashCode(); 138 } 139 isGroup()140 public boolean isGroup() { 141 return false; 142 } 143 144 @Override toString()145 public String toString() { 146 StringBuffer sb = new StringBuffer(); 147 toString(sb); 148 return sb.toString(); 149 } 150 getDefaultValue()151 Comparable<?> getDefaultValue() { 152 return null; 153 } 154 getFirstLeaf()155 Fragment getFirstLeaf() { 156 return this; 157 } 158 getPadValue()159 Comparable<?> getPadValue() { 160 return null; 161 } 162 getQualifier()163 Qualifier getQualifier() { 164 return qualifier; 165 } 166 parse(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)167 boolean parse(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 168 return qualifier.parse(new Fragment[] {this}, 0, segments, version, maxPos, info); 169 } 170 parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)171 abstract boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info); 172 setDefaults(List<Comparable<?>> segments)173 void setDefaults(List<Comparable<?>> segments) { 174 // No-op at this level 175 } 176 toString(StringBuffer sb)177 void toString(StringBuffer sb) { 178 if (!(qualifier == VersionFormatParser.EXACT_ONE_QUALIFIER || (qualifier == VersionFormatParser.ZERO_OR_ONE_QUALIFIER && this.isGroup()))) 179 qualifier.toString(sb); 180 } 181 } 182 183 /** 184 * Specifies the min and max occurrences of a fragment 185 */ 186 static class Qualifier implements Serializable { 187 private static final long serialVersionUID = 7494021832824671685L; 188 189 private final int max; 190 private final int min; 191 Qualifier(int min, int max)192 Qualifier(int min, int max) { 193 this.min = min; 194 this.max = max; 195 } 196 197 @Override equals(Object o)198 public boolean equals(Object o) { 199 if (o == this) 200 return true; 201 if (!(o instanceof Qualifier)) 202 return false; 203 Qualifier oq = (Qualifier) o; 204 return min == oq.min && max == oq.max; 205 } 206 207 @Override hashCode()208 public int hashCode() { 209 return 31 * min + 67 * max; 210 } 211 212 @Override toString()213 public String toString() { 214 StringBuffer sb = new StringBuffer(); 215 toString(sb); 216 return sb.toString(); 217 } 218 getMax()219 int getMax() { 220 return max; 221 } 222 getMin()223 int getMin() { 224 return min; 225 } 226 parse(Fragment[] fragments, int fragIdx, List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)227 boolean parse(Fragment[] fragments, int fragIdx, List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 228 Fragment fragment = fragments[fragIdx++]; 229 int idx = 0; 230 231 // Do the required parsing. I.e. iterate this fragment 232 // min number of times. 233 // 234 for (; idx < min; ++idx) 235 if (!fragment.parseOne(segments, version, maxPos, info)) 236 return false; 237 238 for (; idx < max; ++idx) { 239 // We are greedy. Continue parsing until we get an exception 240 // and remember the state before each parse is performed. 241 // 242 info.pushState(segments.size(), fragment); 243 if (!fragment.parseOne(segments, version, maxPos, info)) { 244 info.popState(segments, fragment); 245 break; 246 } 247 } 248 int maxParsed = idx; 249 250 for (;;) { 251 // Pad with default values unless the max is unbounded 252 // 253 if (idx < max) { 254 if (max != Integer.MAX_VALUE) { 255 for (; idx < max; ++idx) 256 fragment.setDefaults(segments); 257 } 258 } else { 259 if (fragment instanceof StringFragment) { 260 // Check for translations if we default to for MINS or MAXS 261 StringFragment stringFrag = (StringFragment) fragment; 262 Comparable<?> opposite = stringFrag.getOppositeDefaultValue(); 263 if (opposite != null) { 264 idx = segments.size() - 1; 265 if (stringFrag.isOppositeTranslation(segments.get(idx))) 266 segments.set(idx, opposite); 267 } 268 } 269 } 270 271 if (fragIdx == fragments.length) 272 // We are the last segment 273 // 274 return true; 275 276 // Try to parse the next segment. If it fails, pop the state of 277 // this segment (or a child thereof) and try again 278 // 279 if (fragments[fragIdx].getQualifier().parse(fragments, fragIdx, segments, version, maxPos, info)) 280 return true; 281 282 // Be less greedy, step back one position and try again. 283 // 284 if (maxParsed <= min) 285 // We have no more states to pop. Tell previous that we failed. 286 // 287 return false; 288 289 info.popState(segments, fragment); 290 idx = --maxParsed; // segments now have room for one more default value 291 } 292 } 293 toString(StringBuffer sb)294 void toString(StringBuffer sb) { 295 if (min == 0) { 296 switch (max) { 297 case 1: 298 sb.append('?'); 299 break; 300 case Integer.MAX_VALUE: 301 sb.append('*'); 302 break; 303 default: 304 sb.append('{'); 305 sb.append(min); 306 sb.append(','); 307 sb.append(max); 308 sb.append('}'); 309 break; 310 } 311 } else if (max == Integer.MAX_VALUE) { 312 if (min == 1) 313 sb.append('+'); 314 else { 315 sb.append('{'); 316 sb.append(min); 317 sb.append(",}"); //$NON-NLS-1$ 318 } 319 } else { 320 sb.append('{'); 321 sb.append(min); 322 if (min != max) { 323 sb.append(','); 324 sb.append(max); 325 } 326 sb.append('}'); 327 } 328 } 329 330 // Preserve singleton when deserialized readResolve()331 private Object readResolve() { 332 Qualifier q = this; 333 if (min == 0) { 334 if (max == 1) 335 q = VersionFormatParser.ZERO_OR_ONE_QUALIFIER; 336 else if (max == Integer.MAX_VALUE) 337 q = VersionFormatParser.ZERO_OR_MANY_QUALIFIER; 338 } else if (min == 1) { 339 if (max == 1) 340 q = VersionFormatParser.EXACT_ONE_QUALIFIER; 341 else if (max == Integer.MAX_VALUE) 342 q = VersionFormatParser.ONE_OR_MANY_QUALIFIER; 343 } 344 return q; 345 } 346 } 347 348 private static class AutoFragment extends RangeFragment { 349 private static final long serialVersionUID = -1016534328164247755L; 350 AutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)351 AutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 352 super(instr, qualifier); 353 } 354 355 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)356 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 357 int pos = info.getPosition(); 358 maxPos = checkRange(pos, maxPos); 359 if (maxPos < 0) 360 return false; 361 362 char c = version.charAt(pos); 363 if (VersionParser.isDigit(c) && isAllowed(c) && (enumInstruction == null || enumInstruction.isOptional())) { 364 // Parse to next non-digit 365 // 366 int start = pos; 367 int value = c - '0'; 368 while (++pos < maxPos) { 369 c = version.charAt(pos); 370 if (!(VersionParser.isDigit(c) && isAllowed(c))) 371 break; 372 value *= 10; 373 value += (c - '0'); 374 } 375 int len = pos - start; 376 if (rangeMin > len || len > rangeMax) 377 return false; 378 379 if (!isIgnored()) 380 segments.add(VersionParser.valueOf(value)); 381 info.setPosition(pos); 382 return true; 383 } 384 385 int start = pos; 386 if (enumInstruction != null) { 387 int[] posHolder = new int[] {pos}; 388 EnumSegment es = enumInstruction.getEnumSegment(this, version, posHolder, maxPos); 389 if (es != null) { 390 pos = posHolder[0]; 391 int len = pos - start; 392 if (rangeMin > len || len > rangeMax) 393 return false; 394 if (!isIgnored()) 395 segments.add(es); 396 info.setPosition(pos); 397 return true; 398 } 399 if (!enumInstruction.isOptional()) 400 return false; 401 } 402 403 if (!(VersionParser.isLetter(c) && isAllowed(c))) 404 return false; 405 406 // Parse to next non-letter or next delimiter 407 // 408 for (++pos; pos < maxPos; ++pos) { 409 c = version.charAt(pos); 410 if (!(VersionParser.isLetter(c) && isAllowed(c))) 411 break; 412 } 413 int len = pos - start; 414 if (rangeMin > len || len > rangeMax) 415 return false; 416 417 if (!isIgnored()) 418 segments.add(version.substring(start, pos)); 419 info.setPosition(pos); 420 return true; 421 } 422 423 @Override toString(StringBuffer sb)424 void toString(StringBuffer sb) { 425 sb.append('a'); 426 super.toString(sb); 427 } 428 } 429 430 private static class DelimiterFragment extends Fragment { 431 private static final long serialVersionUID = 8173654376143370605L; 432 private final char[] delimChars; 433 private final boolean inverted; 434 DelimiterFragment(VersionFormatParser.Instructions ep, Qualifier qualifier)435 DelimiterFragment(VersionFormatParser.Instructions ep, Qualifier qualifier) { 436 super(qualifier); 437 if (ep == null) { 438 delimChars = null; 439 inverted = false; 440 } else { 441 inverted = ep.inverted; 442 delimChars = ep.characters; 443 } 444 } 445 isMatch(String version, int pos)446 boolean isMatch(String version, int pos) { 447 char c = version.charAt(pos); 448 if (delimChars != null) { 449 for (char delimChar : delimChars) 450 if (c == delimChar) 451 return !inverted; 452 return inverted; 453 } else if (VersionParser.isLetterOrDigit(c)) 454 return false; 455 456 return true; 457 } 458 459 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)460 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 461 int pos = info.getPosition(); 462 if (pos < maxPos && isMatch(version, pos)) { 463 // Just swallow, a delimiter does not contribute to the vector. 464 // 465 info.setPosition(pos + 1); 466 return true; 467 } 468 return false; 469 } 470 471 @Override toString(StringBuffer sb)472 void toString(StringBuffer sb) { 473 sb.append('d'); 474 if (delimChars != null) 475 appendCharacterRange(sb, delimChars, inverted); 476 super.toString(sb); 477 } 478 } 479 appendCharacterRange(StringBuffer sb, char[] range, boolean inverted)480 static void appendCharacterRange(StringBuffer sb, char[] range, boolean inverted) { 481 sb.append('='); 482 sb.append('['); 483 if (inverted) 484 sb.append('^'); 485 int top = range.length; 486 for (int idx = 0; idx < top; ++idx) { 487 char b = range[idx]; 488 if (b == '\\' || b == ']' || (b == '-' && idx + 1 < top)) 489 sb.append('\\'); 490 491 sb.append(b); 492 int ndx = idx + 1; 493 if (ndx + 2 < top) { 494 char c = b; 495 for (; ndx < top; ++ndx) { 496 char n = range[ndx]; 497 if (c + 1 != n) 498 break; 499 c = n; 500 } 501 if (ndx <= idx + 3) 502 continue; 503 504 sb.append('-'); 505 if (c == '\\' || c == ']' || (c == '-' && idx + 1 < top)) 506 sb.append('\\'); 507 sb.append(c); 508 idx = ndx - 1; 509 } 510 } 511 sb.append(']'); 512 sb.append(';'); 513 } 514 createAutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)515 static Fragment createAutoFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 516 return new AutoFragment(instr, qualifier); 517 } 518 createDelimiterFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)519 static Fragment createDelimiterFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 520 return new DelimiterFragment(instr, qualifier); 521 } 522 createGroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array)523 static Fragment createGroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) { 524 return new GroupFragment(instr, qualifier, fragments, array); 525 } 526 createLiteralFragment(Qualifier qualifier, String literal)527 static Fragment createLiteralFragment(Qualifier qualifier, String literal) { 528 return new LiteralFragment(qualifier, literal); 529 } 530 createNumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed)531 static Fragment createNumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) { 532 return new NumberFragment(instr, qualifier, signed); 533 } 534 createPadFragment(Qualifier qualifier)535 static Fragment createPadFragment(Qualifier qualifier) { 536 return new PadFragment(qualifier); 537 } 538 createQuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)539 static Fragment createQuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 540 return new QuotedFragment(instr, qualifier); 541 } 542 createRawFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)543 static Fragment createRawFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 544 return new RawFragment(instr, qualifier); 545 } 546 createStringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean unbound)547 static Fragment createStringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean unbound) { 548 return new StringFragment(instr, qualifier, unbound); 549 } 550 equalsAllowNull(Object a, Object b)551 static boolean equalsAllowNull(Object a, Object b) { 552 return (a == null) ? (b == null) : (b != null && a.equals(b)); 553 } 554 555 private static abstract class ElementFragment extends Fragment { 556 private static final long serialVersionUID = -6834591415456539713L; 557 private final Comparable<?> defaultValue; 558 private final boolean ignored; 559 private final Comparable<?> padValue; 560 ElementFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)561 ElementFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 562 super(qualifier); 563 if (instr != null) { 564 ignored = instr.ignore; 565 defaultValue = instr.defaultValue; 566 padValue = instr.padValue; 567 } else { 568 ignored = false; 569 defaultValue = null; 570 padValue = null; 571 } 572 } 573 574 @Override getDefaultValue()575 Comparable<?> getDefaultValue() { 576 return defaultValue; 577 } 578 579 @Override getPadValue()580 Comparable<?> getPadValue() { 581 return padValue; 582 } 583 isIgnored()584 boolean isIgnored() { 585 return ignored; 586 } 587 588 @Override setDefaults(List<Comparable<?>> segments)589 void setDefaults(List<Comparable<?>> segments) { 590 Comparable<?> defaultVal = getDefaultValue(); 591 if (defaultVal != null) 592 segments.add(defaultVal); 593 } 594 595 @Override toString(StringBuffer sb)596 void toString(StringBuffer sb) { 597 if (ignored) { 598 sb.append('='); 599 sb.append('!'); 600 sb.append(';'); 601 } 602 if (defaultValue != null) { 603 sb.append('='); 604 VersionFormat.rawToString(sb, false, defaultValue); 605 sb.append(';'); 606 } 607 if (padValue != null) { 608 sb.append('='); 609 sb.append('p'); 610 VersionFormat.rawToString(sb, false, padValue); 611 sb.append(';'); 612 } 613 super.toString(sb); 614 } 615 } 616 617 private static class GroupFragment extends ElementFragment { 618 private static final long serialVersionUID = 9219978678087669699L; 619 private final boolean array; 620 private final Fragment[] fragments; 621 GroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array)622 GroupFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, Fragment[] fragments, boolean array) { 623 super(instr, qualifier); 624 this.fragments = fragments; 625 this.array = array; 626 } 627 628 @Override isGroup()629 public boolean isGroup() { 630 return !array; 631 } 632 633 @Override getFirstLeaf()634 Fragment getFirstLeaf() { 635 return fragments[0].getFirstLeaf(); 636 } 637 638 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)639 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 640 if (array) { 641 ArrayList<Comparable<?>> subSegs = new ArrayList<>(); 642 boolean success = fragments[0].getQualifier().parse(fragments, 0, subSegs, version, maxPos, info); 643 if (!success || subSegs.isEmpty()) 644 return false; 645 646 Comparable<?> padValue = info.getPadValue(); 647 if (padValue != null) 648 info.setPadValue(null); // Prevent outer group from getting this. 649 else 650 padValue = getPadValue(); 651 652 padValue = VersionParser.removeRedundantTrail(segments, padValue); 653 segments.add(new VersionVector(subSegs.toArray(new Comparable[subSegs.size()]), padValue)); 654 return true; 655 } 656 657 if (fragments[0].getQualifier().parse(fragments, 0, segments, version, maxPos, info)) { 658 Comparable<?> padValue = getPadValue(); 659 if (padValue != null) 660 info.setPadValue(padValue); 661 return true; 662 } 663 return false; 664 } 665 666 @Override setDefaults(List<Comparable<?>> segments)667 void setDefaults(List<Comparable<?>> segments) { 668 Comparable<?> dflt = getDefaultValue(); 669 if (dflt != null) { 670 // A group default overrides any defaults within the 671 // group fragments 672 super.setDefaults(segments); 673 } else { 674 // Assign defaults for all fragments 675 for (Fragment fragment : fragments) 676 fragment.setDefaults(segments); 677 } 678 } 679 680 @Override toString(StringBuffer sb)681 void toString(StringBuffer sb) { 682 if (array) { 683 sb.append('<'); 684 for (Fragment fragment : fragments) 685 fragment.toString(sb); 686 sb.append('>'); 687 } else { 688 if (getQualifier() == VersionFormatParser.ZERO_OR_ONE_QUALIFIER) { 689 sb.append('['); 690 for (Fragment fragment : fragments) 691 fragment.toString(sb); 692 sb.append(']'); 693 } else { 694 sb.append('('); 695 for (Fragment fragment : fragments) 696 fragment.toString(sb); 697 sb.append(')'); 698 } 699 } 700 super.toString(sb); 701 } 702 } 703 704 private static class LiteralFragment extends Fragment { 705 private static final long serialVersionUID = 6210696245839471802L; 706 private final String string; 707 LiteralFragment(Qualifier qualifier, String string)708 LiteralFragment(Qualifier qualifier, String string) { 709 super(qualifier); 710 this.string = string; 711 } 712 713 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)714 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 715 int pos = info.getPosition(); 716 int litLen = string.length(); 717 if (pos + litLen > maxPos) 718 return false; 719 720 for (int idx = 0; idx < litLen; ++idx, ++pos) { 721 if (string.charAt(idx) != version.charAt(pos)) 722 return false; 723 } 724 info.setPosition(pos); 725 return true; 726 } 727 728 @Override toString(StringBuffer sb)729 void toString(StringBuffer sb) { 730 String str = string; 731 if (str.length() != 1) { 732 sb.append('\''); 733 VersionFormatParser.toStringEscaped(sb, str, "\'"); //$NON-NLS-1$ 734 sb.append('\''); 735 } else { 736 char c = str.charAt(0); 737 switch (c) { 738 case '\'' : 739 case '\\' : 740 case '<' : 741 case '[' : 742 case '(' : 743 case '{' : 744 case '?' : 745 case '*' : 746 case '+' : 747 case '=' : 748 sb.append('\\'); 749 sb.append(c); 750 break; 751 default : 752 if (VersionParser.isLetterOrDigit(c)) { 753 sb.append('\\'); 754 sb.append(c); 755 } else 756 sb.append(c); 757 } 758 } 759 super.toString(sb); 760 } 761 } 762 763 private static class NumberFragment extends RangeFragment { 764 private static final long serialVersionUID = -8552754381106711507L; 765 private final boolean signed; 766 NumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed)767 NumberFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean signed) { 768 super(instr, qualifier); 769 this.signed = signed; 770 } 771 772 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)773 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 774 int pos = info.getPosition(); 775 maxPos = checkRange(pos, maxPos); 776 if (maxPos < 0) 777 return false; 778 779 // Parse to next non-digit 780 // 781 int start = pos; 782 int value; 783 784 char c = version.charAt(pos); 785 if (signed || characters != null) { 786 boolean negate = false; 787 if (signed && c == '-' && pos + 1 < maxPos) { 788 negate = true; 789 c = version.charAt(++pos); 790 } 791 792 if (!(c >= '0' && c <= '9' && isAllowed(c))) 793 return false; 794 795 // Parse to next non-digit 796 // 797 value = c - '0'; 798 while (++pos < maxPos) { 799 c = version.charAt(pos); 800 if (!(c >= '0' && c <= '9' && isAllowed(c))) 801 break; 802 value *= 10; 803 value += (c - '0'); 804 } 805 if (negate) 806 value = -value; 807 } else { 808 if (c < '0' || c > '9') 809 return false; 810 811 // Parse to next non-digit 812 // 813 value = c - '0'; 814 while (++pos < maxPos) { 815 c = version.charAt(pos); 816 if (c < '0' || c > '9') 817 break; 818 value *= 10; 819 value += (c - '0'); 820 } 821 } 822 823 int len = pos - start; 824 if (rangeMin > len || len > rangeMax) 825 return false; 826 827 if (!isIgnored()) 828 segments.add(VersionParser.valueOf(value)); 829 info.setPosition(pos); 830 return true; 831 } 832 833 @Override toString(StringBuffer sb)834 void toString(StringBuffer sb) { 835 sb.append(signed ? 'N' : 'n'); 836 super.toString(sb); 837 } 838 } 839 840 private static class PadFragment extends ElementFragment { 841 private static final long serialVersionUID = 5052010199974380170L; 842 PadFragment(Qualifier qualifier)843 PadFragment(Qualifier qualifier) { 844 super(null, qualifier); 845 } 846 847 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)848 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 849 int pos = info.getPosition(); 850 if (pos >= maxPos || version.charAt(pos) != 'p') 851 return false; 852 853 int[] position = new int[] {++pos}; 854 Comparable<?> v = VersionParser.parseRawElement(version, position, maxPos); 855 if (v == null) 856 return false; 857 858 if (!isIgnored()) 859 info.setPadValue(v); 860 info.setPosition(position[0]); 861 return true; 862 } 863 864 @Override toString(StringBuffer sb)865 void toString(StringBuffer sb) { 866 sb.append('p'); 867 super.toString(sb); 868 } 869 } 870 871 private static class QuotedFragment extends RangeFragment { 872 private static final long serialVersionUID = 6057751133533608969L; 873 QuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)874 QuotedFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 875 super(instr, qualifier); 876 } 877 878 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)879 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 880 int pos = info.getPosition(); 881 if (pos >= maxPos) 882 return false; 883 884 char endQuote; 885 char quote = version.charAt(pos); 886 switch (quote) { 887 case '<' : 888 endQuote = '>'; 889 break; 890 case '{' : 891 endQuote = '}'; 892 break; 893 case '(' : 894 endQuote = ')'; 895 break; 896 case '[' : 897 endQuote = ']'; 898 break; 899 case '>' : 900 endQuote = '<'; 901 break; 902 case '}' : 903 endQuote = '{'; 904 break; 905 case ')' : 906 endQuote = '('; 907 break; 908 case ']' : 909 endQuote = '['; 910 break; 911 default : 912 if (VersionParser.isLetterOrDigit(quote)) 913 return false; 914 endQuote = quote; 915 } 916 int start = ++pos; 917 char c = version.charAt(pos); 918 while (c != endQuote && isAllowed(c) && ++pos < maxPos) 919 c = version.charAt(pos); 920 921 if (c != endQuote || rangeMin > pos - start) 922 // End quote not found 923 return false; 924 925 int len = pos - start; 926 if (rangeMin > len || len > rangeMax) 927 return false; 928 929 if (!isIgnored()) 930 segments.add(version.substring(start, pos)); 931 info.setPosition(++pos); // Skip quote 932 return true; 933 } 934 935 @Override toString(StringBuffer sb)936 void toString(StringBuffer sb) { 937 sb.append('q'); 938 super.toString(sb); 939 } 940 } 941 942 private static abstract class RangeFragment extends ElementFragment { 943 private static final long serialVersionUID = -6680402803630334708L; 944 final char[] characters; 945 final boolean inverted; 946 final int rangeMax; 947 final int rangeMin; 948 final EnumInstruction enumInstruction; 949 RangeFragment(VersionFormatParser.Instructions instr, Qualifier qualifier)950 RangeFragment(VersionFormatParser.Instructions instr, Qualifier qualifier) { 951 super(instr, qualifier); 952 if (instr == null) { 953 characters = null; 954 inverted = false; 955 rangeMin = 0; 956 rangeMax = Integer.MAX_VALUE; 957 enumInstruction = null; 958 } else { 959 characters = instr.characters; 960 inverted = instr.inverted; 961 rangeMin = instr.rangeMin; 962 rangeMax = instr.rangeMax; 963 enumInstruction = instr.enumInstruction; 964 } 965 } 966 967 /** 968 * Checks that pos is at a valid character position, that we 969 * have at least the required minimum characters left, and 970 * if a maximum number of characters is set, limits the 971 * returned value to a maxPos that reflects that maximum. 972 * @param pos the current position 973 * @param maxPos the current maxPos 974 * @return maxPos, possibly limited by rangeMax 975 */ checkRange(int pos, int maxPos)976 int checkRange(int pos, int maxPos) { 977 int check = pos; 978 if (rangeMin == 0) 979 check++; // Verify one character 980 else 981 check += rangeMin; 982 983 if (check > maxPos) 984 // Less then min characters left 985 maxPos = -1; 986 else { 987 if (rangeMax != Integer.MAX_VALUE) { 988 check = pos + rangeMax; 989 if (check < maxPos) 990 maxPos = check; 991 } 992 } 993 return maxPos; 994 } 995 isAllowed(char c)996 boolean isAllowed(char c) { 997 char[] crs = characters; 998 if (crs != null) { 999 int idx = crs.length; 1000 while (--idx >= 0) 1001 if (c == crs[idx]) 1002 return !inverted; 1003 return inverted; 1004 } 1005 return true; 1006 } 1007 1008 @Override toString(StringBuffer sb)1009 void toString(StringBuffer sb) { 1010 if (characters != null) 1011 appendCharacterRange(sb, characters, inverted); 1012 if (rangeMin != 0 || rangeMax != Integer.MAX_VALUE) { 1013 sb.append('='); 1014 sb.append('{'); 1015 sb.append(rangeMin); 1016 if (rangeMin != rangeMax) { 1017 sb.append(','); 1018 if (rangeMax != Integer.MAX_VALUE) 1019 sb.append(rangeMax); 1020 } 1021 sb.append('}'); 1022 sb.append(';'); 1023 } 1024 if (enumInstruction != null) 1025 enumInstruction.toString(sb); 1026 super.toString(sb); 1027 } 1028 } 1029 1030 private static class RawFragment extends ElementFragment { 1031 private static final long serialVersionUID = 4107448125256042602L; 1032 RawFragment(VersionFormatParser.Instructions processing, Qualifier qualifier)1033 RawFragment(VersionFormatParser.Instructions processing, Qualifier qualifier) { 1034 super(processing, qualifier); 1035 } 1036 1037 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)1038 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 1039 int[] position = new int[] {info.getPosition()}; 1040 Comparable<?> v = VersionParser.parseRawElement(version, position, maxPos); 1041 if (v == null) 1042 return false; 1043 1044 if (!isIgnored()) 1045 segments.add(v); 1046 info.setPosition(position[0]); 1047 return true; 1048 } 1049 1050 @Override toString(StringBuffer sb)1051 void toString(StringBuffer sb) { 1052 sb.append('r'); 1053 super.toString(sb); 1054 } 1055 } 1056 1057 private static class StringFragment extends RangeFragment { 1058 private static final long serialVersionUID = -2265924553606430164L; 1059 final boolean anyChar; 1060 private final char oppositeTranslationChar; 1061 private final int oppositeTranslationRepeat; 1062 StringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean noLimit)1063 StringFragment(VersionFormatParser.Instructions instr, Qualifier qualifier, boolean noLimit) { 1064 super(instr, qualifier); 1065 anyChar = noLimit; 1066 char otc = 0; 1067 int otr = 0; 1068 if (instr != null) { 1069 otc = instr.oppositeTranslationChar; 1070 otr = instr.oppositeTranslationRepeat; 1071 if (instr.defaultValue == VersionVector.MINS_VALUE) { 1072 if (otc == 0) 1073 otc = 'z'; 1074 if (otr == 0) 1075 otr = 3; 1076 } else if (instr.defaultValue == VersionVector.MAXS_VALUE) { 1077 if (otc == 0) 1078 otc = '-'; 1079 otr = 1; 1080 } 1081 } 1082 oppositeTranslationChar = otc; 1083 oppositeTranslationRepeat = otr; 1084 } 1085 getOppositeDefaultValue()1086 Comparable<?> getOppositeDefaultValue() { 1087 Comparable<?> dflt = getDefaultValue(); 1088 return dflt == VersionVector.MAXS_VALUE ? VersionVector.MINS_VALUE : (dflt == VersionVector.MINS_VALUE ? VersionVector.MAXS_VALUE : null); 1089 } 1090 isOppositeTranslation(Object val)1091 public boolean isOppositeTranslation(Object val) { 1092 if (val instanceof String) { 1093 String str = (String) val; 1094 int idx = oppositeTranslationRepeat; 1095 if (str.length() == idx) { 1096 while (--idx >= 0) 1097 if (str.charAt(idx) != oppositeTranslationChar) 1098 break; 1099 return idx < 0; 1100 } 1101 } 1102 return false; 1103 } 1104 1105 @Override parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info)1106 boolean parseOne(List<Comparable<?>> segments, String version, int maxPos, TreeInfo info) { 1107 int pos = info.getPosition(); 1108 maxPos = checkRange(pos, maxPos); 1109 if (maxPos < 0) 1110 return false; 1111 1112 int start = pos; 1113 if (enumInstruction != null) { 1114 int[] posHolder = new int[] {pos}; 1115 EnumSegment es = enumInstruction.getEnumSegment(this, version, posHolder, maxPos); 1116 if (es != null) { 1117 pos = posHolder[0]; 1118 int len = pos - start; 1119 if (rangeMin > len || len > rangeMax) 1120 return false; 1121 if (!isIgnored()) 1122 segments.add(es); 1123 info.setPosition(pos); 1124 return true; 1125 } 1126 if (!enumInstruction.isOptional()) 1127 return false; 1128 } 1129 1130 // Parse to next delimiter or end of string 1131 // 1132 if (characters != null) { 1133 if (anyChar) { 1134 // Swallow everything that matches the allowed characters 1135 for (; pos < maxPos; ++pos) { 1136 if (!isAllowed(version.charAt(pos))) 1137 break; 1138 } 1139 } else { 1140 // Swallow letters that matches the allowed characters 1141 for (; pos < maxPos; ++pos) { 1142 char c = version.charAt(pos); 1143 if (!(VersionParser.isLetter(c) && isAllowed(c))) 1144 break; 1145 } 1146 } 1147 } else { 1148 if (anyChar) 1149 // Swallow all characters 1150 pos = maxPos; 1151 else { 1152 // Swallow all letters 1153 for (; pos < maxPos; ++pos) { 1154 if (!VersionParser.isLetter(version.charAt(pos))) 1155 break; 1156 } 1157 } 1158 } 1159 int len = pos - start; 1160 if (len == 0 || rangeMin > len || len > rangeMax) 1161 return false; 1162 1163 if (!isIgnored()) 1164 segments.add(version.substring(start, pos)); 1165 info.setPosition(pos); 1166 return true; 1167 } 1168 1169 @Override toString(StringBuffer sb)1170 void toString(StringBuffer sb) { 1171 sb.append(anyChar ? 'S' : 's'); 1172 super.toString(sb); 1173 } 1174 } 1175 1176 private int current; 1177 1178 private List<Fragment> currentList; 1179 1180 private int eos; 1181 1182 private String format; 1183 1184 private int start; 1185 compile(String fmt, int pos, int maxPos)1186 Fragment compile(String fmt, int pos, int maxPos) throws VersionFormatException { 1187 format = fmt; 1188 if (start >= maxPos) 1189 throw new VersionFormatException(Messages.format_is_empty); 1190 1191 start = pos; 1192 current = pos; 1193 eos = maxPos; 1194 currentList = new ArrayList<>(); 1195 while (current < eos) 1196 parseFragment(); 1197 1198 Fragment topFrag; 1199 switch (currentList.size()) { 1200 case 0 : 1201 throw new VersionFormatException(Messages.format_is_empty); 1202 case 1 : 1203 Fragment frag = currentList.get(0); 1204 if (frag.isGroup()) { 1205 topFrag = frag; 1206 break; 1207 } 1208 // Fall through to default 1209 default : 1210 topFrag = createGroupFragment(null, EXACT_ONE_QUALIFIER, currentList.toArray(new Fragment[currentList.size()]), false); 1211 } 1212 currentList = null; 1213 return topFrag; 1214 } 1215 assertChar(char expected)1216 private void assertChar(char expected) throws VersionFormatException { 1217 if (current >= eos) 1218 throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, new String(new char[] {expected}))); 1219 1220 char c = format.charAt(current); 1221 if (c != expected) 1222 throw formatException(c, new String(new char[] {expected})); 1223 ++current; 1224 } 1225 formatException(char found, String expected)1226 private VersionFormatException formatException(char found, String expected) { 1227 return formatException(new String(new char[] {found}), expected); 1228 } 1229 formatException(String message)1230 private VersionFormatException formatException(String message) { 1231 return new VersionFormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_2, new Object[] {format.substring(start, eos), Integer.valueOf(current), message})); 1232 } 1233 formatException(String found, String expected)1234 private VersionFormatException formatException(String found, String expected) { 1235 return new VersionFormatException(NLS.bind(Messages.syntax_error_in_version_format_0_1_found_2_expected_3, new Object[] {format.substring(start, eos), Integer.valueOf(current), found, expected})); 1236 } 1237 illegalControlCharacter(char c)1238 private VersionFormatException illegalControlCharacter(char c) { 1239 return formatException(NLS.bind(Messages.illegal_character_encountered_ascii_0, VersionParser.valueOf(c))); 1240 } 1241 parseAndConsiderEscapeUntil(char endChar)1242 private String parseAndConsiderEscapeUntil(char endChar) throws VersionFormatException { 1243 StringBuilder sb = new StringBuilder(); 1244 while (current < eos) { 1245 char c = format.charAt(current++); 1246 if (c == endChar) 1247 break; 1248 1249 if (c < 32) 1250 throw illegalControlCharacter(c); 1251 1252 if (c == '\\') { 1253 if (current == eos) 1254 throw formatException(Messages.EOS_after_escape); 1255 c = format.charAt(current++); 1256 if (c < 32) 1257 throw illegalControlCharacter(c); 1258 } 1259 sb.append(c); 1260 } 1261 return sb.toString(); 1262 } 1263 parseAuto()1264 private void parseAuto() throws VersionFormatException { 1265 VersionFormatParser.Instructions ep = parseProcessing(); 1266 if (ep != null) { 1267 if (ep.padValue != null) 1268 throw formatException(Messages.auto_can_not_have_pad_value); 1269 } 1270 currentList.add(createAutoFragment(ep, parseQualifier())); 1271 } 1272 parseBracketGroup()1273 private void parseBracketGroup() throws VersionFormatException { 1274 List<Fragment> saveList = currentList; 1275 currentList = new ArrayList<>(); 1276 while (current < eos && format.charAt(current) != ']') 1277 parseFragment(); 1278 1279 if (current == eos) 1280 throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "]")); //$NON-NLS-1$ 1281 1282 ++current; 1283 VersionFormatParser.Instructions ep = parseProcessing(); 1284 saveList.add(createGroupFragment(ep, ZERO_OR_ONE_QUALIFIER, currentList.toArray(new Fragment[currentList.size()]), false)); 1285 currentList = saveList; 1286 } 1287 parseCharacterGroup(VersionFormatParser.Instructions ep)1288 private void parseCharacterGroup(VersionFormatParser.Instructions ep) throws VersionFormatException { 1289 assertChar('['); 1290 1291 StringBuilder sb = new StringBuilder(); 1292 outer: for (; current < eos; ++current) { 1293 char c = format.charAt(current); 1294 switch (c) { 1295 case '\\' : 1296 if (current + 1 < eos) { 1297 sb.append(format.charAt(++current)); 1298 continue; 1299 } 1300 throw formatException(Messages.premature_end_of_format); 1301 case '^' : 1302 if (sb.length() == 0) 1303 ep.inverted = true; 1304 else 1305 sb.append(c); 1306 continue; 1307 case ']' : 1308 break outer; 1309 case '-' : 1310 if (sb.length() > 0 && current + 1 < eos) { 1311 char rangeEnd = format.charAt(++current); 1312 if (rangeEnd == ']') { 1313 // Use dash verbatim when last in range 1314 sb.append(c); 1315 break outer; 1316 } 1317 1318 char rangeStart = sb.charAt(sb.length() - 1); 1319 if (rangeEnd < rangeStart) 1320 throw formatException(Messages.negative_character_range); 1321 while (++rangeStart <= rangeEnd) 1322 sb.append(rangeStart); 1323 continue; 1324 } 1325 // Fall through to default 1326 default : 1327 if (c < 32) 1328 throw illegalControlCharacter(c); 1329 sb.append(c); 1330 } 1331 } 1332 assertChar(']'); 1333 int top = sb.length(); 1334 char[] chars = new char[top]; 1335 sb.getChars(0, top, chars, 0); 1336 ep.characters = chars; 1337 } 1338 parseDelimiter()1339 private void parseDelimiter() throws VersionFormatException { 1340 VersionFormatParser.Instructions ep = parseProcessing(); 1341 if (ep != null) { 1342 if (ep.rangeMin != 0 || ep.rangeMax != Integer.MAX_VALUE) 1343 throw formatException(Messages.delimiter_can_not_have_range); 1344 if (ep.ignore) 1345 throw formatException(Messages.delimiter_can_not_be_ignored); 1346 if (ep.defaultValue != null) 1347 throw formatException(Messages.delimiter_can_not_have_default_value); 1348 if (ep.padValue != null) 1349 throw formatException(Messages.delimiter_can_not_have_pad_value); 1350 } 1351 currentList.add(createDelimiterFragment(ep, parseQualifier())); 1352 } 1353 parseEnum(Instructions processing)1354 private void parseEnum(Instructions processing) throws VersionFormatException { 1355 ++current; 1356 ArrayList<List<String>> identifiers = new ArrayList<>(); 1357 ArrayList<String> idents = new ArrayList<>(); 1358 StringBuilder sb = new StringBuilder(); 1359 for (;;) { 1360 if (current >= eos) 1361 throw formatException(Messages.bad_enum_definition); 1362 1363 char c = format.charAt(current++); 1364 while (c != '}' && c != ',' && c != '=') { 1365 if (current >= eos || c <= ' ') 1366 throw formatException(Messages.bad_enum_definition); 1367 if (c == '\\') { 1368 c = format.charAt(current++); 1369 if (current >= eos) 1370 throw formatException(Messages.bad_enum_definition); 1371 } 1372 sb.append(c); 1373 c = format.charAt(current++); 1374 } 1375 idents.add(sb.toString()); 1376 sb.setLength(0); 1377 if (c == '=') 1378 continue; 1379 1380 identifiers.add(idents); 1381 if (c == '}') 1382 break; 1383 1384 // c must be ',' at this point 1385 idents = new ArrayList<>(); 1386 } 1387 1388 boolean enumCaseSensitive = true; 1389 boolean enumOptional = false; 1390 boolean enumBegins = false; 1391 OUTER: 1392 while (current < eos) { 1393 char c = format.charAt(current); 1394 switch (c) { 1395 case 'i': 1396 enumCaseSensitive = false; 1397 current++; 1398 break; 1399 case 'b': 1400 enumBegins = true; 1401 current++; 1402 break; 1403 case '?': 1404 enumOptional = true; 1405 current++; 1406 break; 1407 default: 1408 break OUTER; 1409 } 1410 } 1411 1412 // Ensure that all identifiers are unique and make them 1413 // lower case if necessary 1414 HashSet<String> unique = new HashSet<>(); 1415 int ordinal = identifiers.size(); 1416 while (--ordinal >= 0) { 1417 List<String> ids = identifiers.get(ordinal); 1418 int idx = ids.size(); 1419 while (--idx >= 0) { 1420 String id = ids.get(idx); 1421 if (!enumCaseSensitive) 1422 id = id.toLowerCase(); 1423 if (!unique.add(id)) 1424 throw formatException(Messages.bad_enum_definition); 1425 ids.set(idx, id); 1426 } 1427 } 1428 EnumDefinition enumDefinition = EnumDefinition.getEnumDefinition(identifiers); 1429 processing.enumInstruction = new EnumInstruction(enumDefinition, enumCaseSensitive, enumOptional, enumBegins); 1430 } 1431 parseFragment()1432 private void parseFragment() throws VersionFormatException { 1433 if (current == eos) 1434 throw formatException(Messages.premature_end_of_format); 1435 char c = format.charAt(current++); 1436 switch (c) { 1437 case '(' : 1438 parseGroup(false); 1439 break; 1440 case '<' : 1441 parseGroup(true); 1442 break; 1443 case '[' : 1444 parseBracketGroup(); 1445 break; 1446 case 'a' : 1447 parseAuto(); 1448 break; 1449 case 'r' : 1450 parseRaw(); 1451 break; 1452 case 'n' : 1453 parseNumber(false); 1454 break; 1455 case 'N' : 1456 parseNumber(true); 1457 break; 1458 case 's' : 1459 parseString(false); 1460 break; 1461 case 'S' : 1462 parseString(true); 1463 break; 1464 case 'd' : 1465 parseDelimiter(); 1466 break; 1467 case 'q' : 1468 parseQuotedString(); 1469 break; 1470 case 'p' : 1471 parsePad(); 1472 break; 1473 default : 1474 parseLiteral(c); 1475 } 1476 } 1477 parseGroup(boolean array)1478 private void parseGroup(boolean array) throws VersionFormatException { 1479 List<Fragment> saveList = currentList; 1480 currentList = new ArrayList<>(); 1481 char expectedEnd = array ? '>' : ')'; 1482 while (current < eos && format.charAt(current) != expectedEnd) 1483 parseFragment(); 1484 assertChar(expectedEnd); 1485 1486 VersionFormatParser.Instructions ep = parseProcessing(); 1487 if (ep != null) { 1488 if (ep.characters != null) 1489 throw formatException(Messages.array_can_not_have_character_group); 1490 if (ep.rangeMax != Integer.MAX_VALUE && ep.padValue != null) 1491 throw formatException(Messages.cannot_combine_range_upper_bound_with_pad_value); 1492 if (ep.enumInstruction != null) 1493 throw formatException(Messages.array_can_not_have_enum); 1494 } 1495 1496 if (currentList.isEmpty()) 1497 throw formatException(array ? Messages.array_can_not_be_empty : Messages.group_can_not_be_empty); 1498 saveList.add(createGroupFragment(ep, parseQualifier(), currentList.toArray(new Fragment[currentList.size()]), array)); 1499 currentList = saveList; 1500 } 1501 parseIntegerLiteral()1502 private int parseIntegerLiteral() throws VersionFormatException { 1503 if (current == eos) 1504 throw formatException(NLS.bind(Messages.premature_end_of_format_expected_0, "<integer>")); //$NON-NLS-1$ 1505 1506 char c = format.charAt(current); 1507 if (!VersionParser.isDigit(c)) 1508 throw formatException(c, "<integer>"); //$NON-NLS-1$ 1509 1510 int value = c - '0'; 1511 while (++current < eos) { 1512 c = format.charAt(current); 1513 if (!VersionParser.isDigit(c)) 1514 break; 1515 value *= 10; 1516 value += (c - '0'); 1517 } 1518 return value; 1519 } 1520 parseLiteral(char c)1521 private void parseLiteral(char c) throws VersionFormatException { 1522 String value; 1523 switch (c) { 1524 case '\'' : 1525 value = parseAndConsiderEscapeUntil(c); 1526 break; 1527 case ')' : 1528 case ']' : 1529 case '{' : 1530 case '}' : 1531 case '?' : 1532 case '*' : 1533 throw formatException(c, "<literal>"); //$NON-NLS-1$ 1534 default : 1535 if (VersionParser.isLetterOrDigit(c)) 1536 throw formatException(c, "<literal>"); //$NON-NLS-1$ 1537 1538 if (c < 32) 1539 throw illegalControlCharacter(c); 1540 1541 if (c == '\\') { 1542 if (current == eos) 1543 throw formatException(Messages.EOS_after_escape); 1544 c = format.charAt(current++); 1545 if (c < 32) 1546 throw illegalControlCharacter(c); 1547 } 1548 value = new String(new char[] {c}); 1549 } 1550 currentList.add(createLiteralFragment(parseQualifier(), value)); 1551 } 1552 parseMinMax()1553 private int[] parseMinMax() throws VersionFormatException { 1554 1555 int max = Integer.MAX_VALUE; 1556 ++current; 1557 int min = parseIntegerLiteral(); 1558 char c = format.charAt(current); 1559 if (c == '}') { 1560 max = min; 1561 if (max == 0) 1562 throw formatException(Messages.range_max_cannot_be_zero); 1563 ++current; 1564 } else if (c == ',' && current + 1 < eos) { 1565 if (format.charAt(++current) != '}') { 1566 max = parseIntegerLiteral(); 1567 if (max == 0) 1568 throw formatException(Messages.range_max_cannot_be_zero); 1569 if (max < min) 1570 throw formatException(Messages.range_max_cannot_be_less_then_range_min); 1571 } 1572 assertChar('}'); 1573 } else 1574 throw formatException(c, "},"); //$NON-NLS-1$ 1575 return new int[] {min, max}; 1576 } 1577 parseNumber(boolean signed)1578 private void parseNumber(boolean signed) throws VersionFormatException { 1579 VersionFormatParser.Instructions ep = parseProcessing(); 1580 if (ep != null) { 1581 if (ep.padValue != null) 1582 throw formatException(Messages.number_can_not_have_pad_value); 1583 } 1584 currentList.add(createNumberFragment(ep, parseQualifier(), signed)); 1585 } 1586 parsePad()1587 private void parsePad() throws VersionFormatException { 1588 currentList.add(createPadFragment(parseQualifier())); 1589 } 1590 parseProcessing()1591 private VersionFormatParser.Instructions parseProcessing() throws VersionFormatException { 1592 if (current >= eos) 1593 return null; 1594 1595 char c = format.charAt(current); 1596 if (c != '=') 1597 return null; 1598 1599 VersionFormatParser.Instructions ep = new VersionFormatParser.Instructions(); 1600 do { 1601 current++; 1602 parseProcessingInstruction(ep); 1603 } while (current < eos && format.charAt(current) == '='); 1604 return ep; 1605 } 1606 parseProcessingInstruction(VersionFormatParser.Instructions processing)1607 private void parseProcessingInstruction(VersionFormatParser.Instructions processing) throws VersionFormatException { 1608 if (current == eos) 1609 throw formatException(Messages.premature_end_of_format); 1610 1611 char c = format.charAt(current); 1612 switch (c) { 1613 case 'p': 1614 // =pad(<raw-element>); 1615 // 1616 if (processing.padValue != null) 1617 throw formatException(Messages.pad_defined_more_then_once); 1618 if (processing.ignore) 1619 throw formatException(Messages.cannot_combine_ignore_with_other_instruction); 1620 ++current; 1621 processing.padValue = parseRawElement(); 1622 break; 1623 case '!': 1624 // =ignore; 1625 // 1626 if (processing.ignore) 1627 throw formatException(Messages.ignore_defined_more_then_once); 1628 if (processing.padValue != null || processing.characters != null || processing.rangeMin != 0 || processing.rangeMax != Integer.MAX_VALUE || processing.defaultValue != null) 1629 throw formatException(Messages.cannot_combine_ignore_with_other_instruction); 1630 ++current; 1631 processing.ignore = true; 1632 break; 1633 case '[': 1634 // =[<character group]; 1635 // 1636 if (processing.characters != null) 1637 throw formatException(Messages.character_group_defined_more_then_once); 1638 if (processing.ignore) 1639 throw formatException(Messages.cannot_combine_ignore_with_other_instruction); 1640 parseCharacterGroup(processing); 1641 break; 1642 case '{': 1643 if (current + 1 == eos) 1644 throw formatException(Messages.premature_end_of_format); 1645 if (VersionParser.isDigit(format.charAt(current + 1))) { 1646 // ={min,max}; 1647 // 1648 if (processing.rangeMin != 0 || processing.rangeMax != Integer.MAX_VALUE) 1649 throw formatException(Messages.range_defined_more_then_once); 1650 if (processing.ignore) 1651 throw formatException(Messages.cannot_combine_ignore_with_other_instruction); 1652 int[] minMax = parseMinMax(); 1653 processing.rangeMin = minMax[0]; 1654 processing.rangeMax = minMax[1]; 1655 } else { 1656 // ={enum1,enum2,...}; 1657 // 1658 if (processing.enumInstruction != null) 1659 throw formatException(Messages.enum_defined_more_then_once); 1660 parseEnum(processing); 1661 } 1662 break; 1663 default: 1664 // =<raw-element>; 1665 if (processing.defaultValue != null) 1666 throw formatException(Messages.default_defined_more_then_once); 1667 if (processing.ignore) 1668 throw formatException(Messages.cannot_combine_ignore_with_other_instruction); 1669 Comparable<?> dflt = parseRawElement(); 1670 processing.defaultValue = dflt; 1671 if (current < eos && format.charAt(current) == '{') { 1672 // =m{<translated min char>} 1673 // =''{<translated max char>,<max char repeat>} 1674 if (++current == eos) 1675 throw formatException(Messages.premature_end_of_format); 1676 processing.oppositeTranslationChar = format.charAt(current++); 1677 if (current == eos) 1678 throw formatException(Messages.premature_end_of_format); 1679 1680 if (dflt == VersionVector.MINS_VALUE) { 1681 processing.oppositeTranslationRepeat = 3; 1682 if (format.charAt(current) == ',') { 1683 ++current; 1684 processing.oppositeTranslationRepeat = parseIntegerLiteral(); 1685 } 1686 } else if (dflt != VersionVector.MAXS_VALUE) { 1687 current -= 2; 1688 throw formatException(Messages.only_max_and_empty_string_defaults_can_have_translations); 1689 } 1690 assertChar('}'); 1691 } 1692 break; 1693 } 1694 assertChar(';'); 1695 } 1696 parseQualifier()1697 private Qualifier parseQualifier() throws VersionFormatException { 1698 if (current >= eos) 1699 return EXACT_ONE_QUALIFIER; 1700 1701 char c = format.charAt(current); 1702 if (c == '?') { 1703 ++current; 1704 return ZERO_OR_ONE_QUALIFIER; 1705 } 1706 1707 if (c == '*') { 1708 ++current; 1709 return ZERO_OR_MANY_QUALIFIER; 1710 } 1711 1712 if (c == '+') { 1713 ++current; 1714 return ONE_OR_MANY_QUALIFIER; 1715 } 1716 1717 if (c != '{') 1718 return EXACT_ONE_QUALIFIER; 1719 1720 int[] minMax = parseMinMax(); 1721 int min = minMax[0]; 1722 int max = minMax[1]; 1723 1724 // Use singletons for commonly used ranges 1725 // 1726 if (min == 0) { 1727 if (max == 1) 1728 return ZERO_OR_ONE_QUALIFIER; 1729 if (max == Integer.MAX_VALUE) 1730 return ZERO_OR_MANY_QUALIFIER; 1731 } else if (min == 1) { 1732 if (max == 1) 1733 return EXACT_ONE_QUALIFIER; 1734 if (max == Integer.MAX_VALUE) 1735 return ONE_OR_MANY_QUALIFIER; 1736 } 1737 return new Qualifier(min, max); 1738 } 1739 parseQuotedString()1740 private void parseQuotedString() throws VersionFormatException { 1741 VersionFormatParser.Instructions ep = parseProcessing(); 1742 if (ep != null) { 1743 if (ep.padValue != null) 1744 throw formatException(Messages.string_can_not_have_pad_value); 1745 } 1746 currentList.add(createQuotedFragment(ep, parseQualifier())); 1747 } 1748 parseRaw()1749 private void parseRaw() throws VersionFormatException { 1750 VersionFormatParser.Instructions ep = parseProcessing(); 1751 if (ep != null) { 1752 if (ep.padValue != null) 1753 throw formatException(Messages.raw_element_can_not_have_pad_value); 1754 } 1755 currentList.add(createRawFragment(ep, parseQualifier())); 1756 } 1757 parseRawElement()1758 private Comparable<?> parseRawElement() throws VersionFormatException { 1759 int[] position = new int[] {current}; 1760 Comparable<?> v = VersionParser.parseRawElement(format, position, eos); 1761 if (v == null) 1762 throw new VersionFormatException(NLS.bind(Messages.raw_element_expected_0, format)); 1763 current = position[0]; 1764 return v; 1765 } 1766 parseString(boolean unlimited)1767 private void parseString(boolean unlimited) throws VersionFormatException { 1768 VersionFormatParser.Instructions ep = parseProcessing(); 1769 if (ep != null) { 1770 if (ep.padValue != null) 1771 throw formatException(Messages.string_can_not_have_pad_value); 1772 } 1773 currentList.add(createStringFragment(ep, parseQualifier(), unlimited)); 1774 } 1775 toStringEscaped(StringBuffer sb, String value, String escapes)1776 static void toStringEscaped(StringBuffer sb, String value, String escapes) { 1777 for (int idx = 0; idx < value.length(); ++idx) { 1778 char c = value.charAt(idx); 1779 if (c == '\\' || escapes.indexOf(c) >= 0) 1780 sb.append('\\'); 1781 sb.append(c); 1782 } 1783 } 1784 } 1785