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