1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.files.sp3;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Scanner;
28 import java.util.function.Function;
29 import java.util.regex.Pattern;
30
31 import org.hipparchus.exception.LocalizedCoreFormats;
32 import org.hipparchus.geometry.euclidean.threed.Vector3D;
33 import org.hipparchus.util.FastMath;
34 import org.orekit.annotation.DefaultDataContext;
35 import org.orekit.data.DataContext;
36 import org.orekit.data.DataSource;
37 import org.orekit.errors.OrekitException;
38 import org.orekit.errors.OrekitIllegalArgumentException;
39 import org.orekit.errors.OrekitMessages;
40 import org.orekit.files.general.EphemerisFileParser;
41 import org.orekit.frames.Frame;
42 import org.orekit.gnss.IGSUtils;
43 import org.orekit.gnss.TimeSystem;
44 import org.orekit.time.AbsoluteDate;
45 import org.orekit.time.DateComponents;
46 import org.orekit.time.DateTimeComponents;
47 import org.orekit.time.TimeComponents;
48 import org.orekit.time.TimeScale;
49 import org.orekit.time.TimeScales;
50 import org.orekit.utils.CartesianDerivativesFilter;
51 import org.orekit.utils.Constants;
52
53
54
55
56
57
58
59
60
61
62
63
64
65 public class SP3Parser implements EphemerisFileParser<SP3> {
66
67
68 public static final int DEFAULT_INTERPOLATION_SAMPLES = 7;
69
70
71 private static final String SPACES = "\\s+";
72
73
74 private final double mu;
75
76
77 private final int interpolationSamples;
78
79
80 private final Function<? super String, ? extends Frame> frameBuilder;
81
82
83 private final TimeScales timeScales;
84
85
86
87
88
89
90
91
92
93
94
95 @DefaultDataContext
96 public SP3Parser() {
97 this(Constants.EIGEN5C_EARTH_MU, DEFAULT_INTERPOLATION_SAMPLES, IGSUtils::guessFrame);
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 @DefaultDataContext
117 public SP3Parser(final double mu,
118 final int interpolationSamples,
119 final Function<? super String, ? extends Frame> frameBuilder) {
120 this(mu, interpolationSamples, frameBuilder,
121 DataContext.getDefault().getTimeScales());
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 public SP3Parser(final double mu,
138 final int interpolationSamples,
139 final Function<? super String, ? extends Frame> frameBuilder,
140 final TimeScales timeScales) {
141 this.mu = mu;
142 this.interpolationSamples = interpolationSamples;
143 this.frameBuilder = frameBuilder;
144 this.timeScales = timeScales;
145 }
146
147 @Override
148 public SP3 parse(final DataSource source) {
149
150 try (Reader reader = source.getOpener().openReaderOnce();
151 BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {
152
153 if (br == null) {
154 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
155 }
156
157
158 final ParseInfo pi = new ParseInfo(source.getName(), this);
159
160 int lineNumber = 0;
161 Iterable<LineParser> candidateParsers = Collections.singleton(LineParser.HEADER_VERSION);
162 nextLine:
163 for (String line = br.readLine(); line != null; line = br.readLine()) {
164 ++lineNumber;
165 for (final LineParser candidate : candidateParsers) {
166 if (candidate.canHandle(line)) {
167 try {
168 candidate.parse(line, pi);
169 if (pi.done) {
170 break nextLine;
171 }
172 candidateParsers = candidate.allowedNext();
173 continue nextLine;
174 } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
175 throw new OrekitException(e,
176 OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
177 lineNumber, pi.fileName, line);
178 }
179 }
180 }
181
182
183 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
184 lineNumber, pi.fileName, line);
185
186 }
187
188 pi.file.validate(true, pi.fileName);
189 return pi.file;
190
191 } catch (IOException ioe) {
192 throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
193 }
194
195 }
196
197
198
199
200
201
202 private static class ParseInfo {
203
204
205
206
207 private final String fileName;
208
209
210 private final SP3Parser parser;
211
212
213 private SP3 file;
214
215
216 private AbsoluteDate latestEpoch;
217
218
219 private Vector3D latestPosition;
220
221
222
223
224 private Vector3D latestPositionAccuracy;
225
226
227 private double latestClock;
228
229
230
231
232 private double latestClockAccuracy;
233
234
235
236
237 private boolean latestClockEvent;
238
239
240
241
242 private boolean latestClockPrediction;
243
244
245
246
247 private boolean latestOrbitManeuverEvent;
248
249
250
251
252 private boolean latestOrbitPrediction;
253
254
255 private boolean hasVelocityEntries;
256
257
258 private TimeScale timeScale;
259
260
261 private DateTimeComponents epoch;
262
263
264 private int maxSatellites;
265
266
267 private int nbAccuracies;
268
269
270 private boolean done;
271
272
273
274
275
276 protected ParseInfo(final String fileName,
277 final SP3Parser parser) {
278 this.fileName = fileName;
279 this.parser = parser;
280 latestEpoch = null;
281 latestPosition = null;
282 latestClock = 0.0;
283 hasVelocityEntries = false;
284 epoch = DateTimeComponents.JULIAN_EPOCH;
285 timeScale = parser.timeScales.getGPS();
286 maxSatellites = 0;
287 nbAccuracies = 0;
288 done = false;
289 }
290 }
291
292
293 private enum LineParser {
294
295
296 HEADER_VERSION("^#[a-z].*") {
297
298
299 @Override
300 public void parse(final String line, final ParseInfo pi) {
301 try (Scanner s1 = new Scanner(line);
302 Scanner s2 = s1.useDelimiter(SPACES);
303 Scanner scanner = s2.useLocale(Locale.US)) {
304 scanner.skip("#");
305 final String v = scanner.next();
306
307 final SP3Header header = new SP3Header();
308 header.setVersion(v.substring(0, 1).toLowerCase().charAt(0));
309
310 pi.hasVelocityEntries = "V".equals(v.substring(1, 2));
311 header.setFilter(pi.hasVelocityEntries ?
312 CartesianDerivativesFilter.USE_PV :
313 CartesianDerivativesFilter.USE_P);
314
315 final int year = Integer.parseInt(v.substring(2));
316 final int month = scanner.nextInt();
317 final int day = scanner.nextInt();
318 final int hour = scanner.nextInt();
319 final int minute = scanner.nextInt();
320 final double second = scanner.nextDouble();
321
322 pi.epoch = new DateTimeComponents(year, month, day,
323 hour, minute, second);
324
325 final int numEpochs = scanner.nextInt();
326 header.setNumberOfEpochs(numEpochs);
327
328
329 final String fullSpec = scanner.next();
330 final List<DataUsed> dataUsed = new ArrayList<>();
331 for (final String specifier : fullSpec.split("\\+")) {
332 dataUsed.add(DataUsed.parse(specifier, pi.fileName, header.getVersion()));
333 }
334 header.setDataUsed(dataUsed);
335
336 header.setCoordinateSystem(scanner.next());
337 header.setOrbitTypeKey(scanner.next());
338 header.setAgency(scanner.hasNext() ? scanner.next() : "");
339 pi.file = new SP3(header, pi.parser.mu, pi.parser.interpolationSamples,
340 pi.parser.frameBuilder.apply(header.getCoordinateSystem()));
341 }
342 }
343
344
345 @Override
346 public Iterable<LineParser> allowedNext() {
347 return Collections.singleton(HEADER_DATE_TIME_REFERENCE);
348 }
349
350 },
351
352
353 HEADER_DATE_TIME_REFERENCE("^##.*") {
354
355
356 @Override
357 public void parse(final String line, final ParseInfo pi) {
358 try (Scanner s1 = new Scanner(line);
359 Scanner s2 = s1.useDelimiter(SPACES);
360 Scanner scanner = s2.useLocale(Locale.US)) {
361 scanner.skip("##");
362
363
364 pi.file.getHeader().setGpsWeek(scanner.nextInt());
365
366 pi.file.getHeader().setSecondsOfWeek(scanner.nextDouble());
367
368 pi.file.getHeader().setEpochInterval(scanner.nextDouble());
369
370 pi.file.getHeader().setModifiedJulianDay(scanner.nextInt());
371
372 pi.file.getHeader().setDayFraction(scanner.nextDouble());
373 }
374 }
375
376
377 @Override
378 public Iterable<LineParser> allowedNext() {
379 return Collections.singleton(HEADER_SAT_IDS);
380 }
381
382 },
383
384
385 HEADER_SAT_IDS("^\\+ .*") {
386
387
388 @Override
389 public void parse(final String line, final ParseInfo pi) {
390
391 if (pi.maxSatellites == 0) {
392
393 pi.maxSatellites = Integer.parseInt(line.substring(3, 6).trim());
394 }
395
396 final int lineLength = line.length();
397 int count = pi.file.getSatelliteCount();
398 int startIdx = 9;
399 while (count++ < pi.maxSatellites && (startIdx + 3) <= lineLength) {
400 final String satId = line.substring(startIdx, startIdx + 3).trim();
401 if (!satId.isEmpty()) {
402 pi.file.addSatellite(satId);
403 }
404 startIdx += 3;
405 }
406 }
407
408
409 @Override
410 public Iterable<LineParser> allowedNext() {
411 return Arrays.asList(HEADER_SAT_IDS, HEADER_ACCURACY);
412 }
413
414 },
415
416
417 HEADER_ACCURACY("^\\+\\+.*") {
418
419
420 @Override
421 public void parse(final String line, final ParseInfo pi) {
422 final int lineLength = line.length();
423 int startIdx = 9;
424 while (pi.nbAccuracies < pi.maxSatellites && (startIdx + 3) <= lineLength) {
425 final String sub = line.substring(startIdx, startIdx + 3).trim();
426 if (!sub.isEmpty()) {
427 final int exponent = Integer.parseInt(sub);
428
429 pi.file.getHeader().setAccuracy(pi.nbAccuracies++,
430 SP3Utils.siAccuracy(SP3Utils.POSITION_ACCURACY_UNIT,
431 SP3Utils.POS_VEL_BASE_ACCURACY,
432 exponent));
433 }
434 startIdx += 3;
435 }
436 }
437
438
439 @Override
440 public Iterable<LineParser> allowedNext() {
441 return Arrays.asList(HEADER_ACCURACY, HEADER_TIME_SYSTEM);
442 }
443
444 },
445
446
447 HEADER_TIME_SYSTEM("^%c.*") {
448
449
450 @Override
451 public void parse(final String line, final ParseInfo pi) {
452
453 if (pi.file.getHeader().getType() == null) {
454
455 pi.file.getHeader().setType(SP3FileType.parse(line.substring(3, 5).trim()));
456
457
458 final String tsStr = line.substring(9, 12).trim();
459 final TimeSystem ts;
460 if (tsStr.equalsIgnoreCase("ccc")) {
461 ts = TimeSystem.GPS;
462 } else {
463 ts = TimeSystem.parseTimeSystem(tsStr);
464 }
465 pi.file.getHeader().setTimeSystem(ts);
466 pi.timeScale = ts.getTimeScale(pi.parser.timeScales);
467
468
469 pi.file.getHeader().setEpoch(new AbsoluteDate(pi.epoch, pi.timeScale));
470 }
471
472 }
473
474
475 @Override
476 public Iterable<LineParser> allowedNext() {
477 return Arrays.asList(HEADER_TIME_SYSTEM, HEADER_STANDARD_DEVIATIONS);
478 }
479
480 },
481
482
483 HEADER_STANDARD_DEVIATIONS("^%f.*") {
484
485
486 @Override
487 public void parse(final String line, final ParseInfo pi) {
488 final double posVelBase = Double.parseDouble(line.substring(3, 13).trim());
489 if (posVelBase != 0.0) {
490
491 pi.file.getHeader().setPosVelBase(posVelBase);
492 }
493
494 final double clockBase = Double.parseDouble(line.substring(14, 26).trim());
495 if (clockBase != 0.0) {
496
497 pi.file.getHeader().setClockBase(clockBase);
498 }
499 }
500
501
502 @Override
503 public Iterable<LineParser> allowedNext() {
504 return Arrays.asList(HEADER_STANDARD_DEVIATIONS, HEADER_CUSTOM_PARAMETERS);
505 }
506
507 },
508
509
510 HEADER_CUSTOM_PARAMETERS("^%i.*") {
511
512
513 @Override
514 public void parse(final String line, final ParseInfo pi) {
515
516 }
517
518
519 @Override
520 public Iterable<LineParser> allowedNext() {
521 return Arrays.asList(HEADER_CUSTOM_PARAMETERS, HEADER_COMMENTS);
522 }
523
524 },
525
526
527 HEADER_COMMENTS("^[%]?/\\*.*|") {
528
529
530 @Override
531 public void parse(final String line, final ParseInfo pi) {
532 pi.file.getHeader().addComment(line.substring(line.indexOf('*') + 1).trim());
533 }
534
535
536 @Override
537 public Iterable<LineParser> allowedNext() {
538 return Arrays.asList(HEADER_COMMENTS, DATA_EPOCH);
539 }
540
541 },
542
543
544 DATA_EPOCH("^\\* .*") {
545
546
547 @Override
548 public void parse(final String line, final ParseInfo pi) {
549 final int year;
550 final int month;
551 final int day;
552 final int hour;
553 final int minute;
554 final double second;
555 try (Scanner s1 = new Scanner(line);
556 Scanner s2 = s1.useDelimiter(SPACES);
557 Scanner scanner = s2.useLocale(Locale.US)) {
558 scanner.skip("\\*");
559 year = scanner.nextInt();
560 month = scanner.nextInt();
561 day = scanner.nextInt();
562 hour = scanner.nextInt();
563 minute = scanner.nextInt();
564 second = scanner.nextDouble();
565 }
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608 DateComponents dc = day == 0 ?
609 new DateComponents(new DateComponents(year, month, 1), -1) :
610 new DateComponents(year, month, day);
611 final List<AbsoluteDate> candidates = new ArrayList<>();
612 int h = hour;
613 int m = minute;
614 double s = second;
615 if (s >= 60.0) {
616 s -= 60;
617 addCandidate(candidates, dc, h, m, s, pi.timeScale);
618 m++;
619 }
620 if (m > 59) {
621 m = 0;
622 addCandidate(candidates, dc, h, m, s, pi.timeScale);
623 h++;
624 }
625 if (h > 23) {
626 h = 0;
627 addCandidate(candidates, dc, h, m, s, pi.timeScale);
628 dc = new DateComponents(dc, 1);
629 }
630 addCandidate(candidates, dc, h, m, s, pi.timeScale);
631 final AbsoluteDate expected = pi.latestEpoch == null ?
632 pi.file.getHeader().getEpoch() :
633 pi.latestEpoch.shiftedBy(pi.file.getHeader().getEpochInterval());
634 pi.latestEpoch = null;
635 for (final AbsoluteDate candidate : candidates) {
636 if (FastMath.abs(candidate.durationFrom(expected)) < 0.01 * pi.file.getHeader().getEpochInterval()) {
637 pi.latestEpoch = candidate;
638 }
639 }
640 if (pi.latestEpoch == null) {
641
642
643 pi.latestEpoch = new AbsoluteDate(year, month, day, hour, minute, second, pi.timeScale);
644 }
645
646 }
647
648
649
650
651
652
653
654
655
656
657 private void addCandidate(final List<AbsoluteDate> candidates, final DateComponents dc,
658 final int hour, final int minute, final double second,
659 final TimeScale timeScale) {
660 try {
661 candidates.add(new AbsoluteDate(dc, new TimeComponents(hour, minute, second), timeScale));
662 } catch (OrekitIllegalArgumentException oiae) {
663
664 }
665 }
666
667
668 @Override
669 public Iterable<LineParser> allowedNext() {
670 return Collections.singleton(DATA_POSITION);
671 }
672
673 },
674
675
676 DATA_POSITION("^P.*") {
677
678
679 @Override
680 public void parse(final String line, final ParseInfo pi) {
681 final String satelliteId = line.substring(1, 4).trim();
682
683 if (!pi.file.containsSatellite(satelliteId)) {
684 pi.latestPosition = Vector3D.ZERO;
685 } else {
686
687 final SP3Header header = pi.file.getHeader();
688
689
690 pi.latestPosition = new Vector3D(SP3Utils.POSITION_UNIT.toSI(Double.parseDouble(line.substring(4, 18).trim())),
691 SP3Utils.POSITION_UNIT.toSI(Double.parseDouble(line.substring(18, 32).trim())),
692 SP3Utils.POSITION_UNIT.toSI(Double.parseDouble(line.substring(32, 46).trim())));
693
694
695 final double clockField = line.trim().length() <= 46 ?
696 SP3Utils.DEFAULT_CLOCK_VALUE :
697 Double.parseDouble(line.substring(46, 60).trim());
698 pi.latestClock = FastMath.abs(clockField - SP3Utils.DEFAULT_CLOCK_VALUE) < 1.0e-6 ?
699 Double.NaN : SP3Utils.CLOCK_UNIT.toSI(clockField);
700
701 if (pi.latestPosition.getNorm() > 0) {
702
703 if (line.length() < 69 ||
704 line.substring(61, 63).trim().isEmpty() ||
705 line.substring(64, 66).trim().isEmpty() ||
706 line.substring(67, 69).trim().isEmpty()) {
707 pi.latestPositionAccuracy = null;
708 } else {
709 pi.latestPositionAccuracy = new Vector3D(SP3Utils.siAccuracy(SP3Utils.POSITION_ACCURACY_UNIT,
710 header.getPosVelBase(),
711 Integer.parseInt(line.substring(61, 63).trim())),
712 SP3Utils.siAccuracy(SP3Utils.POSITION_ACCURACY_UNIT,
713 header.getPosVelBase(),
714 Integer.parseInt(line.substring(64, 66).trim())),
715 SP3Utils.siAccuracy(SP3Utils.POSITION_ACCURACY_UNIT,
716 header.getPosVelBase(),
717 Integer.parseInt(line.substring(67, 69).trim())));
718 }
719
720 if (line.length() < 73 || line.substring(70, 73).trim().isEmpty()) {
721 pi.latestClockAccuracy = Double.NaN;
722 } else {
723 pi.latestClockAccuracy = SP3Utils.siAccuracy(SP3Utils.CLOCK_ACCURACY_UNIT,
724 header.getClockBase(),
725 Integer.parseInt(line.substring(70, 73).trim()));
726 }
727
728 pi.latestClockEvent = line.length() >= 75 && line.charAt(74) == 'E';
729 pi.latestClockPrediction = line.length() >= 76 && line.charAt(75) == 'P';
730 pi.latestOrbitManeuverEvent = line.length() >= 79 && line.charAt(78) == 'M';
731 pi.latestOrbitPrediction = line.length() >= 80 && line.charAt(79) == 'P';
732
733 if (!pi.hasVelocityEntries) {
734 final SP3Coordinate coord =
735 new SP3Coordinate(pi.latestEpoch,
736 pi.latestPosition, pi.latestPositionAccuracy,
737 Vector3D.ZERO, null,
738 pi.latestClock, pi.latestClockAccuracy,
739 0.0, Double.NaN,
740 pi.latestClockEvent, pi.latestClockPrediction,
741 pi.latestOrbitManeuverEvent, pi.latestOrbitPrediction);
742 pi.file.getEphemeris(satelliteId).addCoordinate(coord, header.getEpochInterval());
743 }
744 }
745 }
746 }
747
748
749 @Override
750 public Iterable<LineParser> allowedNext() {
751 return Arrays.asList(DATA_EPOCH, DATA_POSITION, DATA_POSITION_CORRELATION, DATA_VELOCITY, EOF);
752 }
753
754 },
755
756
757 DATA_POSITION_CORRELATION("^EP.*") {
758
759
760 @Override
761 public void parse(final String line, final ParseInfo pi) {
762
763 }
764
765
766 @Override
767 public Iterable<LineParser> allowedNext() {
768 return Arrays.asList(DATA_EPOCH, DATA_POSITION, DATA_VELOCITY, EOF);
769 }
770
771 },
772
773
774 DATA_VELOCITY("^V.*") {
775
776
777 @Override
778 public void parse(final String line, final ParseInfo pi) {
779 final String satelliteId = line.substring(1, 4).trim();
780
781 if (pi.file.containsSatellite(satelliteId) && pi.latestPosition.getNorm() > 0) {
782
783 final SP3Header header = pi.file.getHeader();
784
785
786 final Vector3D velocity = new Vector3D(SP3Utils.VELOCITY_UNIT.toSI(Double.parseDouble(line.substring(4, 18).trim())),
787 SP3Utils.VELOCITY_UNIT.toSI(Double.parseDouble(line.substring(18, 32).trim())),
788 SP3Utils.VELOCITY_UNIT.toSI(Double.parseDouble(line.substring(32, 46).trim())));
789
790
791 final double clockRateField = line.trim().length() <= 46 ?
792 SP3Utils.DEFAULT_CLOCK_RATE_VALUE :
793 Double.parseDouble(line.substring(46, 60).trim());
794 final double clockRateChange = FastMath.abs(clockRateField - SP3Utils.DEFAULT_CLOCK_RATE_VALUE) < 1.0e-6 ?
795 Double.NaN : SP3Utils.CLOCK_RATE_UNIT.toSI(clockRateField);
796
797 final Vector3D velocityAccuracy;
798 if (line.length() < 69 ||
799 line.substring(61, 63).trim().isEmpty() ||
800 line.substring(64, 66).trim().isEmpty() ||
801 line.substring(67, 69).trim().isEmpty()) {
802 velocityAccuracy = null;
803 } else {
804 velocityAccuracy = new Vector3D(SP3Utils.siAccuracy(SP3Utils.VELOCITY_ACCURACY_UNIT,
805 header.getPosVelBase(),
806 Integer.parseInt(line.substring(61, 63).trim())),
807 SP3Utils.siAccuracy(SP3Utils.VELOCITY_ACCURACY_UNIT,
808 header.getPosVelBase(),
809 Integer.parseInt(line.substring(64, 66).trim())),
810 SP3Utils.siAccuracy(SP3Utils.VELOCITY_ACCURACY_UNIT,
811 header.getPosVelBase(),
812 Integer.parseInt(line.substring(67, 69).trim())));
813 }
814
815 final double clockRateAccuracy;
816 if (line.length() < 73 || line.substring(70, 73).trim().isEmpty()) {
817 clockRateAccuracy = Double.NaN;
818 } else {
819 clockRateAccuracy = SP3Utils.siAccuracy(SP3Utils.CLOCK_RATE_ACCURACY_UNIT,
820 header.getClockBase(),
821 Integer.parseInt(line.substring(70, 73).trim()));
822 }
823
824 final SP3Coordinate coord =
825 new SP3Coordinate(pi.latestEpoch,
826 pi.latestPosition, pi.latestPositionAccuracy,
827 velocity, velocityAccuracy,
828 pi.latestClock, pi.latestClockAccuracy,
829 clockRateChange, clockRateAccuracy,
830 pi.latestClockEvent, pi.latestClockPrediction,
831 pi.latestOrbitManeuverEvent, pi.latestOrbitPrediction);
832 pi.file.getEphemeris(satelliteId).addCoordinate(coord, header.getEpochInterval());
833 }
834 }
835
836
837 @Override
838 public Iterable<LineParser> allowedNext() {
839 return Arrays.asList(DATA_EPOCH, DATA_POSITION, DATA_VELOCITY_CORRELATION, EOF);
840 }
841
842 },
843
844
845 DATA_VELOCITY_CORRELATION("^EV.*") {
846
847
848 @Override
849 public void parse(final String line, final ParseInfo pi) {
850
851 }
852
853
854 @Override
855 public Iterable<LineParser> allowedNext() {
856 return Arrays.asList(DATA_EPOCH, DATA_POSITION, EOF);
857 }
858
859 },
860
861
862 EOF("^[eE][oO][fF]\\s*$") {
863
864
865 @Override
866 public void parse(final String line, final ParseInfo pi) {
867 pi.done = true;
868 }
869
870
871 @Override
872 public Iterable<LineParser> allowedNext() {
873 return Collections.singleton(EOF);
874 }
875
876 };
877
878
879 private final Pattern pattern;
880
881
882
883
884 LineParser(final String lineRegexp) {
885 pattern = Pattern.compile(lineRegexp);
886 }
887
888
889
890
891
892 public abstract void parse(String line, ParseInfo pi);
893
894
895
896
897 public abstract Iterable<LineParser> allowedNext();
898
899
900
901
902
903 public boolean canHandle(final String line) {
904 return pattern.matcher(line).matches();
905 }
906
907 }
908
909 }