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