code: /*******************************************************************************
   * Copyright 2018 572682
   * 
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   * use this file except in compliance with the License.  You may obtain a copy
   * of the License at
   * 
   *   http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
   * License for the specific language governing permissions and limitations under
   * the License.
   ******************************************************************************/
  package us.dot.its.jpo.ode.importer.parser;
  
  import java.io.BufferedInputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.math.BigDecimal;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import us.dot.its.jpo.ode.model.OdeBsmMetadata;
  import us.dot.its.jpo.ode.model.OdeBsmMetadata.BsmSource;
  import us.dot.its.jpo.ode.model.OdeLogMetadata;
  import us.dot.its.jpo.ode.model.OdeLogMetadata.RecordType;
  import us.dot.its.jpo.ode.model.OdeLogMsgMetadataLocation;
  import us.dot.its.jpo.ode.model.OdeSpatMetadata;
  import us.dot.its.jpo.ode.model.OdeSpatMetadata.SpatSource;
  import us.dot.its.jpo.ode.model.ReceivedMessageDetails;
  import us.dot.its.jpo.ode.model.RxSource;
  import us.dot.its.jpo.ode.plugin.j2735.builders.ElevationBuilder;
  import us.dot.its.jpo.ode.plugin.j2735.builders.HeadingBuilder;
  import us.dot.its.jpo.ode.plugin.j2735.builders.LatitudeBuilder;
  import us.dot.its.jpo.ode.plugin.j2735.builders.LongitudeBuilder;
  import us.dot.its.jpo.ode.plugin.j2735.builders.SpeedOrVelocityBuilder;
  import us.dot.its.jpo.ode.util.DateTimeUtils;
  
  public abstract class LogFileParser implements FileParser {
  	private static final Logger logger = LoggerFactory.getLogger(LogFileParser.class);
  
  	public static final int BUFFER_SIZE = 4096;
  
  	protected byte[] readBuffer = new byte[BUFFER_SIZE];
  	protected int step = 0;
  
  	protected String filename;
  	protected RecordType recordType;
  
  	protected IntersectionParser intersectionParser;
  	protected LocationParser locationParser;
  	protected TimeParser timeParser;
  	protected SecurityResultCodeParser secResCodeParser;
  	protected PayloadParser payloadParser;
  
  	public LogFileParser() {
  		super();
  	}
  
  	public static LogFileParser factory(String fileName) {
  		LogFileParser fileParser;
  		if (fileName.startsWith(RecordType.bsmTx.name())) {
  			logger.debug("Parsing as \"Transmit BSM \" log file type.");
  			fileParser = new BsmLogFileParser().setRecordType(RecordType.bsmTx);
  		} else if (fileName.startsWith(RecordType.bsmLogDuringEvent.name())) {
  			logger.debug("Parsing as \"BSM For Event\" log file type.");
  			fileParser = new BsmLogFileParser().setRecordType(RecordType.bsmLogDuringEvent);
  		} else if (fileName.startsWith(RecordType.rxMsg.name())) {
  			logger.debug("Parsing as \"Received Messages\" log file type.");
  			fileParser = new RxMsgFileParser().setRecordType(RecordType.rxMsg);
  		} else if (fileName.startsWith(RecordType.dnMsg.name())) {
  			logger.debug("Parsing as \"Distress Notifications\" log file type.");
  			fileParser = new DistressMsgFileParser().setRecordType(RecordType.dnMsg);
  		} else if (fileName.startsWith(RecordType.driverAlert.name())) {
  			logger.debug("Parsing as \"Driver Alert\" log file type.");
  			fileParser = new DriverAlertFileParser().setRecordType(RecordType.driverAlert);
  		} else if(fileName.startsWith(RecordType.spatTx.name())) {
  			logger.debug("Parsing as \"Transmit SPAT\" log file type.");
  			fileParser = new SpatLogFileParser().setRecordType(RecordType.spatTx);
  		}
  		else {
  			throw new IllegalArgumentException("Unknown log file prefix: " + fileName);
  		}
  		return fileParser;
  	}
  
  	public ParserStatus parseFile(BufferedInputStream bis, String fileName) throws FileParserException {
  
  		ParserStatus status = ParserStatus.INIT;
  		if (getStep() == 0) {
  			setFilename(fileName);
  			setStep(getStep() + 1);
  		}
  		status = ParserStatus.COMPLETE;
  
  		return status;
  	}
  
  	public ParserStatus parseStep(BufferedInputStream bis, int length) throws FileParserException {
  		if (length > BUFFER_SIZE) {
  			throw new FileParserException(
  					"Data size of " + length + " is larger than allocated buffer size of " + BUFFER_SIZE);
  		}
  
  		try {
  			int numBytes;
  			if (bis.markSupported()) {
  				bis.mark(length);
  			}
  			numBytes = bis.read(readBuffer, 0, length);
  			if (numBytes < 0) {
  				return ParserStatus.EOF;
  			} else if (numBytes < length) {
  				if (bis.markSupported()) {
  					try {
  						bis.reset();
  					} catch (IOException ioe) {
  						throw new FileParserException("Error reseting Input Stream to marked position", ioe);
  					}
  				}
  				return ParserStatus.PARTIAL;
  			} else {
  				step++;
  				return ParserStatus.COMPLETE;
  			}
  		} catch (Exception e) {
  			throw new FileParserException("Error parsing step " + step, e);
  		}
  	}
  
  	public int getStep() {
  		return step;
  	}
  
  	public LogFileParser setStep(int step) {
  		this.step = step;
  		return this;
  	}
  
  	protected int resetStep() {
  		return setStep(0).getStep();
  	}
  
  	protected ParserStatus nextStep(BufferedInputStream bis, String fileName, LogFileParser parser)
  			throws FileParserException {
  
  		ParserStatus status = parser.parseFile(bis, fileName);
  		if (status == ParserStatus.COMPLETE) {
  			step++;
  		}
  		return status;
  	}
  
  	public String getFilename() {
  		return filename;
  	}
  
  	public LogFileParser setFilename(String filename) {
  		this.filename = filename;
  		return this;
  	}
  
  	public RecordType getRecordType() {
  		return recordType;
  	}
  
  	public LogFileParser setRecordType(RecordType recordType) {
  		this.recordType = recordType;
  		return this;
  	}
  
  	public LocationParser getLocationParser() {
  		return locationParser;
  	}
  
  	public void setLocationParser(LocationParser locationParser) {
  		this.locationParser = locationParser;
  	}
  
  	public TimeParser getTimeParser() {
  		return timeParser;
  	}
  
  	public void setTimeParser(TimeParser timeParser) {
  		this.timeParser = timeParser;
  	}
  
  	public SecurityResultCodeParser getSecResCodeParser() {
  		return secResCodeParser;
  	}
  
  	public void setSecResCodeParser(SecurityResultCodeParser secResCodeParser) {
  		this.secResCodeParser = secResCodeParser;
  	}
  
  	public PayloadParser getPayloadParser() {
  		return payloadParser;
  	}
  
  	public void setPayloadParser(PayloadParser payloadParser) {
  		this.payloadParser = payloadParser;
  	}
  
  	public IntersectionParser getIntersectionParser() {
  		return intersectionParser;
  	}
  
  	public void setIntersectionParser(IntersectionParser intersectionParser) {
  		this.intersectionParser = intersectionParser;
  	}
  
  	public void updateMetadata(OdeLogMetadata metadata) {
  		metadata.setLogFileName(getFilename());
  		metadata.setRecordType(getRecordType());
  		metadata.setRecordGeneratedAt(DateTimeUtils.isoDateTime(getTimeParser().getGeneratedAt()));
  
  		if (getSecResCodeParser() != null) {
  			metadata.setSecurityResultCode(getSecResCodeParser().getSecurityResultCode());
  		}
  
  		metadata.setReceivedMessageDetails(buildReceivedMessageDetails(this));
  
  		if (metadata instanceof OdeBsmMetadata) {
  			OdeBsmMetadata odeBsmMetadata = (OdeBsmMetadata) metadata;
  			BsmSource bsmSource = BsmSource.unknown;
  			if (this instanceof BsmLogFileParser) {
  				BsmLogFileParser bsmLogFileParser = (BsmLogFileParser) this;
  				bsmSource = bsmLogFileParser.getBsmSource();
  			} else if (this instanceof RxMsgFileParser) {
  				RxMsgFileParser rxMsgFileParser = (RxMsgFileParser) this;
  				if (rxMsgFileParser.getRxSource() == RxSource.RV) {
  					bsmSource = BsmSource.RV;
  				}
  			}
  			odeBsmMetadata.setBsmSource(bsmSource);
  		}
  		if (metadata instanceof OdeSpatMetadata) {
  			OdeSpatMetadata odeSpatMetadata = (OdeSpatMetadata) metadata;
  			SpatSource spatSource = SpatSource.unknown;
  			boolean isCertPresent = true; /*ieee 1609 (acceptable values 0 = no,1 =yes by default the Cert shall be present)*/
  			if (this instanceof SpatLogFileParser) {
  				SpatLogFileParser spatLogFileParser = (SpatLogFileParser) this;
  				spatSource = spatLogFileParser.getSpatSource();
  				isCertPresent = spatLogFileParser.isCertPresent(); //update
  			}
  			odeSpatMetadata.setSpatSource(spatSource);
  			odeSpatMetadata.setIsCertPresent(isCertPresent);
  		}
  
  		metadata.calculateGeneratedBy();
  	}
  
  	private static ReceivedMessageDetails buildReceivedMessageDetails(LogFileParser parser) {
  		LocationParser locationParser = parser.getLocationParser();
  		ReceivedMessageDetails rxMsgDetails = null;
  		if (locationParser != null) {
  			LogLocation locationDetails = locationParser.getLocation();
  			BigDecimal genericLatitude = LatitudeBuilder.genericLatitude(locationDetails.getLatitude());
  			BigDecimal genericLongitude = LongitudeBuilder.genericLongitude(locationDetails.getLongitude());
  			BigDecimal genericElevation = ElevationBuilder.genericElevation(locationDetails.getElevation());
  			BigDecimal genericSpeedOrVelocity = SpeedOrVelocityBuilder
  					.genericSpeedOrVelocity(locationDetails.getSpeed());
  			BigDecimal genericHeading = HeadingBuilder.genericHeading(locationDetails.getHeading());
  			rxMsgDetails = new ReceivedMessageDetails(new OdeLogMsgMetadataLocation(
  					genericLatitude == null ? null : genericLatitude.stripTrailingZeros().toPlainString(),
  					genericLongitude == null ? null : genericLongitude.stripTrailingZeros().toPlainString(),
  					genericElevation == null ? null : genericElevation.stripTrailingZeros().toPlainString(),
  					genericSpeedOrVelocity == null ? null : genericSpeedOrVelocity.stripTrailingZeros().toPlainString(),
  					genericHeading == null ? null : genericHeading.stripTrailingZeros().toPlainString()), null);
  		}
  
  		if (rxMsgDetails != null) {
  			if (parser instanceof RxMsgFileParser) {
  				RxMsgFileParser rxMsgFileParser = (RxMsgFileParser) parser;
  				rxMsgDetails.setRxSource(rxMsgFileParser.getRxSource());
  			} else {
  				rxMsgDetails.setRxSource(RxSource.NA);
  			}
  		}
  
  		return rxMsgDetails;
  	}
  
  	public void writeTo(OutputStream os) throws IOException {
  		locationParser.writeTo(os);
  		timeParser.writeTo(os);
  		secResCodeParser.writeTo(os);
  		payloadParser.writeTo(os);
  	}
  }
  