/* 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.packet;
import static java.util.Objects.requireNonNull;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.ISODateTimeFormat;
import dk.dma.ais.message.AisMessage;
import dk.dma.ais.message.AisPosition;
import dk.dma.ais.message.IPositionMessage;
import dk.dma.commons.util.io.OutputStreamSink;
/**
*
* @author Kasper Nielsen
*/
class AisPacketOutputSinkTable extends OutputStreamSink<AisPacket> {
/** Just a placeholder for NULL in the methods map. */
private static final Method NULL = AisPacketOutputSinkTable.class.getDeclaredMethods()[0];
/** The column we are writing. */
private final String[] columns;
/** A cache of methods. */
private final ConcurrentHashMap<Map.Entry<Class<?>, String>, Method> methods = new ConcurrentHashMap<>();
/** A date formatter. */
private final DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
/** A date time formatter for utc. */
private final DateTimeFormatter utc = new DateTimeFormatterBuilder().appendDayOfMonth(2).appendLiteral('-')
.appendMonthOfYear(2).appendLiteral('-').appendYear(2, 2).appendLiteral(' ').appendHourOfDay(2)
.appendLiteral(':').appendMinuteOfHour(2).appendLiteral(':').appendSecondOfMinute(2).toFormatter();
final byte[] separator;
final boolean writeHeader;
public AisPacketOutputSinkTable(String format, boolean writeHeader) {
this(format, writeHeader, ";");
}
public AisPacketOutputSinkTable(String format, boolean writeHeader, String separator) {
columns = format.split(";");
this.writeHeader = writeHeader;
this.separator = requireNonNull(separator).getBytes(StandardCharsets.US_ASCII);
}
/** {@inheritDoc} */
@Override
public void process(OutputStream stream, AisPacket packet, long count) throws IOException {
AisMessage m = packet.tryGetAisMessage();
if (m != null) {
for (int i = 0; i < columns.length; i++) {
String c = columns[i];
// Special columns
if (c.equals("n")) {
stream.write(Long.toString(count).getBytes(StandardCharsets.US_ASCII));
} else if (c.equals("timestamp")) {
stream.write(Long.toString(packet.getBestTimestamp()).getBytes(StandardCharsets.US_ASCII));
} else if (c.equals("utc")) {
DateTime dateTime = new DateTime(new Date(packet.getBestTimestamp()));
String str = utc.print(dateTime);
stream.write(str.getBytes(StandardCharsets.US_ASCII));
} else if (c.equals("time")) {
DateTime dateTime = new DateTime(new Date(packet.getBestTimestamp()));
String str = fmt.print(dateTime);
stream.write(str.getBytes(StandardCharsets.US_ASCII));
} else if (c.equals("mmsi")) {
String str = Integer.toString(m.getUserId());
stream.write(str.getBytes(StandardCharsets.US_ASCII));
} else if ((c.equals("lat") || c.equals("lon")) && m instanceof IPositionMessage) {
AisPosition pos = ((IPositionMessage) m).getPos();
if (pos != null) {
if (c.equals("lat")) {
stream.write(Double.toString(pos.getLatitudeDouble()).getBytes(StandardCharsets.US_ASCII));
} else {
stream.write(Double.toString(pos.getLongitudeDouble()).getBytes(StandardCharsets.US_ASCII));
}
}
} else {
// Okay use reflection
Method g = findGetter(c, m.getClass());
if (g != null) {
try {
Object o = g.invoke(m);
String s = o.toString();
stream.write(s.getBytes(StandardCharsets.US_ASCII));
} catch (InvocationTargetException | IllegalAccessException e) {
throw new IOException(e);
}
}
}
// Write separator for all but the last column
if (i < columns.length - 1) {
stream.write(separator);
}
}
stream.write('\n');
}
}
/** {@inheritDoc} */
@Override
public void header(OutputStream stream) throws IOException {
// Writes the name of each column as the header
if (writeHeader) {
for (int i = 0; i < columns.length; i++) {
stream.write(columns[i].getBytes(StandardCharsets.US_ASCII));
if (i < columns.length - 1) {
stream.write(separator);
}
}
stream.write('\n');
}
}
private Method findGetter(String nameOfColumn, Class<?> type) throws IOException {
Entry<Class<?>, String> key = new SimpleImmutableEntry<Class<?>, String>(type, nameOfColumn);
Method m = methods.get(key);
if (m == null) {
m = NULL;
BeanInfo info = null;
try {
info = Introspector.getBeanInfo(type);
} catch (IntrospectionException e) {
throw new IOException(e);
}
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
if (nameOfColumn.equals(pd.getName())) {
m = pd.getReadMethod();
}
}
methods.put(key, m);
}
return m == NULL ? null : m;
}
}