/* Copyright (c) 2011 Danish Maritime Authority.
*
* 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 dk.dma.ais.message;
import dk.dma.ais.binary.BinArray;
import dk.dma.ais.binary.SixbitEncoder;
import dk.dma.ais.binary.SixbitException;
import dk.dma.ais.proprietary.IProprietarySourceTag;
import dk.dma.ais.proprietary.IProprietaryTag;
import dk.dma.ais.sentence.Vdm;
import dk.dma.enav.model.geometry.Position;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
/**
* Abstract base class for all AIS messages
*/
public abstract class AisMessage implements Serializable {
/** serialVersionUID. */
private static final long serialVersionUID = 1L;
/** A set of all valid AIS message types. */
public static final Set<Integer> VALID_MESSAGE_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(1,
2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 17, 18, 19, 21, 24)));
protected int msgId; // 6 bit: message id
protected int repeat; // 2 bit: How many times message has been repeated
protected int userId; // 30 bit: MMSI number
protected transient Vdm vdm; // The VDM encapsulating the AIS message
/**
* Constructor given message id
*
* @param msgId
*/
public AisMessage(int msgId) {
this.msgId = msgId;
this.repeat = 0;
}
/**
* Constructor given VDM with AIS message
*
* @param vdm
*/
public AisMessage(Vdm vdm) {
this.vdm = vdm;
this.msgId = vdm.getMsgId();
}
/**
* Base parse method to be called by all extending classes
*
* @param binArray
* @throws AisMessageException
* @throws SixbitException
*/
protected void parse(BinArray binArray) throws AisMessageException, SixbitException {
this.repeat = (int) binArray.getVal(2);
this.userId = (int) binArray.getVal(30);
}
/**
* Base encode method to be called by all extending classes
*
* @return SixbitEncoder
*/
protected SixbitEncoder encode() {
SixbitEncoder encoder = new SixbitEncoder();
encoder.addVal(msgId, 6);
encoder.addVal(repeat, 2);
encoder.addVal(userId, 30);
return encoder;
}
/**
* Abstract method to be implemented by all extending classes
*
* @return SixbitEncoder
*/
public abstract SixbitEncoder getEncoded();
public int getMsgId() {
return msgId;
}
public int getRepeat() {
return repeat;
}
public void setRepeat(int repeat) {
this.repeat = repeat;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
/**
* Return the LAST source tag (closest to AIS sentence)
*
* @return
*/
public IProprietarySourceTag getSourceTag() {
LinkedList<IProprietaryTag> tags = vdm == null ? null : vdm.getTags();
if (tags == null) {
return null;
}
// Iterate backwards
for (Iterator<IProprietaryTag> iterator = tags.descendingIterator(); iterator.hasNext();) {
IProprietaryTag tag = iterator.next();
if (tag instanceof IProprietarySourceTag) {
return (IProprietarySourceTag) tag;
}
}
return null;
}
/**
* Get all tags
*
* @return
*/
public LinkedList<IProprietaryTag> getTags() {
return vdm.getTags();
}
/**
* Add tag (to front)
*
* @param sourceTag
*/
public void setTag(IProprietaryTag tag) {
LinkedList<IProprietaryTag> tags = vdm.getTags();
if (tags == null) {
tags = new LinkedList<>();
}
tags.addFirst(tag);
}
public void setTags(LinkedList<IProprietaryTag> tags) {
vdm.setTags(tags);
}
/**
* Get VDM this message was encapsulated in
*
* @return Vdm
*/
public Vdm getVdm() {
return vdm;
}
/**
* Returns a valid position if this message has a valid position, otherwise null.
*
* @return a valid position if this message has a valid position, otherwise null
*/
public Position getValidPosition() {
return null;
}
/**
* Returns the target type of the message or <code>null</code> if the message does not have a target type.
*
* @return the target type of the message or <code>null</code> if the message does not have a target type
*/
public AisTargetType getTargetType() {
// TODO do we need to check target type also, or is the mmsi number enough???
if (userId >= 970_000_000 && userId <= 970_999_999) {
return AisTargetType.SART;
}
Class<? extends AisMessage> type = getClass();
if (AisMessage4.class.isAssignableFrom(type)) {
return AisTargetType.BS;
} else if (AisMessage21.class.isAssignableFrom(type)) {
return AisTargetType.ATON;
} else if (AisMessage18.class.isAssignableFrom(type) || AisMessage19.class.isAssignableFrom(type)
|| AisMessage24.class.isAssignableFrom(type)) {
return AisTargetType.B;
} else if (AisPositionMessage.class.isAssignableFrom(type) || AisMessage5.class.isAssignableFrom(type)
|| AisMessage27.class.isAssignableFrom(type)) {
return AisTargetType.A;
}
return null;
}
/**
* Given VDM return the encapsulated AIS message. To determine which message is returned use instanceof operator or
* getMsgId() before casting.
*
* Example: AisMessage aisMessage = AisMessage.getInstance(vmd); if (aisMessage instanceof AisPositionMessage) {
* AisPositionMessage posMessage = (AisPositionMessage)aisMessage; } ...
*
* @param vdm
* @return AisMessage
* @throws AisMessageException
* @throws SixbitException
*/
public static AisMessage getInstance(Vdm vdm) throws AisMessageException, SixbitException {
AisMessage message;
switch (vdm.getMsgId()) {
case 1:
message = new AisMessage1(vdm);
vdm.getBinArray().doneReading();
break;
case 2:
message = new AisMessage2(vdm);
break;
case 3:
message = new AisMessage3(vdm);
vdm.getBinArray().doneReading();
break;
case 4:
message = new AisMessage4(vdm);
break;
case 5:
message = new AisMessage5(vdm);
break;
case 6:
message = new AisMessage6(vdm);
break;
case 7:
message = new AisMessage7(vdm);
break;
case 8:
message = new AisMessage8(vdm);
break;
case 9:
message = new AisMessage9(vdm);
break;
case 10:
message = new AisMessage10(vdm);
break;
case 11:
message = new AisMessage11(vdm);
break;
case 12:
message = new AisMessage12(vdm);
break;
case 13:
message = new AisMessage13(vdm);
break;
case 14:
message = new AisMessage14(vdm);
break;
case 15:
// TODO implement real message class
message = new AisUnsupportedMessageType(vdm);
break;
case 16:
// TODO implement real message class
message = new AisUnsupportedMessageType(vdm);
break;
case 17:
message = new AisMessage17(vdm);
break;
case 18:
message = new AisMessage18(vdm);
break;
case 19:
message = new AisMessage19(vdm);
break;
case 20:
// TODO implement real message class
message = new AisUnsupportedMessageType(vdm);
break;
case 21:
message = new AisMessage21(vdm);
break;
case 22:
// TODO implement real message class
message = new AisUnsupportedMessageType(vdm);
break;
case 23:
// TODO implement real message class
message = new AisUnsupportedMessageType(vdm);
break;
case 24:
message = new AisMessage24(vdm);
break;
case 27:
message = new AisMessage27(vdm);
break;
default:
throw new AisMessageException("Unknown AIS message id " + vdm.getMsgId());
}
return message;
}
/**
* Utility to trim text from AIS message
*
* @param text
* @return
*/
public static String trimText(String text) {
if (text == null) {
return null;
}
// Remove @
int firstAt = text.indexOf("@");
if (firstAt >= 0) {
text = text.substring(0, firstAt);
}
// Trim leading and trailing spaces
return text.trim();
}
/**
* Method for reassembling original message appending possible proprietary source tags
*
* @return
*/
public String reassemble() {
LinkedList<IProprietaryTag> tags = vdm.getTags();
StringBuilder buf = new StringBuilder();
if (tags != null) {
for (IProprietaryTag tag : tags) {
if (tag.getSentence() != null) {
buf.append(tag.getSentence() + "\r\n");
}
}
}
buf.append(getVdm().getOrgLinesJoined());
return buf.toString();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[msgId=");
builder.append(msgId);
builder.append(", repeat=");
builder.append(repeat);
builder.append(", userId=");
builder.append(userId);
return builder.toString();
}
}