/*
* Copyright 2011-16 Fraunhofer ISE
*
* This file is part of OpenMUC.
* For more information visit http://www.openmuc.org
*
* OpenMUC is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenMUC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenMUC. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openmuc.framework.datalogger.ascii;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import org.openmuc.framework.data.Flag;
import org.openmuc.framework.data.Record;
import org.openmuc.framework.data.Value;
import org.openmuc.framework.data.ValueType;
import org.openmuc.framework.datalogger.ascii.exceptions.WrongCharacterException;
import org.openmuc.framework.datalogger.ascii.exceptions.WrongScalingException;
import org.openmuc.framework.datalogger.ascii.utils.Const;
import org.openmuc.framework.datalogger.ascii.utils.IESDataFormatUtils;
import org.openmuc.framework.datalogger.ascii.utils.LogRecordContainerAscii;
import org.openmuc.framework.datalogger.ascii.utils.LoggerUtils;
import org.openmuc.framework.datalogger.spi.LogChannel;
import org.openmuc.framework.datalogger.spi.LogRecordContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogFileWriter {
private final String directoryPath;
private static final Logger logger = LoggerFactory.getLogger(LogFileWriter.class);
private File actualFile;
private final boolean isFillUpFiles;
public LogFileWriter(String directoryPath, boolean isFillUpFiles) {
this.isFillUpFiles = isFillUpFiles;
this.directoryPath = directoryPath;
}
/**
* Main logger writing controller.
*
* @param group
* log interval container group
* @param loggingInterval
* logging interval
* @param logTimeOffset
* logging time offset
* @param calendar
* calendar of current time
* @param logChannelList
* logging channel list
*/
public void log(LogIntervalContainerGroup group, int loggingInterval, int logTimeOffset, Calendar calendar,
HashMap<String, LogChannel> logChannelList) {
PrintStream out = getStream(group, loggingInterval, logTimeOffset, calendar, logChannelList);
if (out == null) {
return;
}
List<LogRecordContainer> logRecordContainer = group.getList();
// TODO match column with container id, so that they don't get mixed up
if (isFillUpFiles) {
fillUpFile(loggingInterval, logTimeOffset, calendar, logChannelList, logRecordContainer, out);
}
String logLine = getLoggingLine(logRecordContainer, logChannelList, calendar, false);
out.print(logLine); // print because of println makes different newline char on different systems
out.flush();
out.close();
}
private void fillUpFile(int loggingInterval, int logTimeOffset, Calendar calendar,
HashMap<String, LogChannel> logChannelList, List<LogRecordContainer> logRecordContainer, PrintStream out) {
Long lastLoglineTimestamp = AsciiLogger.getLastLoggedLineTimeStamp(loggingInterval, logTimeOffset);
if (lastLoglineTimestamp != null && lastLoglineTimestamp > 0) {
long diff = calendar.getTimeInMillis() - lastLoglineTimestamp;
if (diff >= loggingInterval) {
Calendar errCalendar = new GregorianCalendar(Locale.getDefault());
errCalendar.setTimeInMillis(lastLoglineTimestamp);
if (errCalendar.get(Calendar.DAY_OF_YEAR) == calendar.get(Calendar.DAY_OF_YEAR)
&& errCalendar.get(Calendar.YEAR) == calendar.get(Calendar.YEAR)) {
long numOfErrorLines = diff / loggingInterval;
for (int i = 1; i < numOfErrorLines; ++i) {
errCalendar.setTimeInMillis(lastLoglineTimestamp + ((long) loggingInterval * i));
out.print(getLoggingLine(logRecordContainer, logChannelList, errCalendar, true));
}
}
}
}
}
private String getLoggingLine(List<LogRecordContainer> logRecordContainer,
HashMap<String, LogChannel> logChannelList, Calendar calendar, boolean isError32) {
StringBuilder sb = new StringBuilder();
LoggerUtils.setLoggerTimestamps(sb, calendar);
for (int i = 0; i < logRecordContainer.size(); i++) {
String value = "";
int size = Const.VALUE_SIZE_MINIMAL;
boolean left = true;
Record record = logRecordContainer.get(i).getRecord();
String channelID = logRecordContainer.get(i).getChannelId();
LogChannel logChannel = logChannelList.get(channelID);
if (record != null) {
Value recordValue = record.getValue();
Record recordBackup = null;
if (isError32) {
recordBackup = logRecordContainer.get(i).getRecord();
logRecordContainer.set(i,
new LogRecordContainerAscii(channelID, new Record(Flag.DATA_LOGGING_NOT_ACTIVE)));
}
record = logRecordContainer.get(i).getRecord();
if (record.getFlag() == Flag.VALID) {
if (recordValue == null) {
// write error flag
value = LoggerUtils.buildError(Flag.CANNOT_WRITE_NULL_VALUE);
size = getDataTypeSize(logChannel, i);
}
else {
ValueType valueType = logChannel.getValueType();
switch (valueType) {
case BOOLEAN:
value = String.valueOf(recordValue.asShort());
break;
case LONG:
value = String.valueOf(recordValue.asLong());
size = Const.VALUE_SIZE_LONG;
break;
case INTEGER:
value = String.valueOf(recordValue.asInt());
size = Const.VALUE_SIZE_INTEGER;
break;
case SHORT:
value = String.valueOf(recordValue.asShort());
size = Const.VALUE_SIZE_SHORT;
break;
case DOUBLE:
case FLOAT:
size = Const.VALUE_SIZE_DOUBLE;
try {
value = IESDataFormatUtils.convertDoubleToStringWithMaxLength(recordValue.asDouble(),
size);
} catch (WrongScalingException e) {
value = LoggerUtils.buildError(Flag.UNKNOWN_ERROR);
logger.error(e.getMessage() + " ChannelId: " + channelID);
}
break;
case BYTE_ARRAY:
left = false;
size = checkMinimalValueSize(getDataTypeSize(logChannel, i));
byte[] byteArray = recordValue.asByteArray();
if (byteArray.length > size) {
value = LoggerUtils.buildError(Flag.UNKNOWN_ERROR);
logger.error("The byte array is too big, length is " + byteArray.length
+ " but max. length allowed is " + size + ", ChannelId: " + channelID);
}
else {
value = Const.HEXADECIMAL + LoggerUtils.byteArrayToHexString(byteArray);
}
break;
case STRING:
left = false;
size = checkMinimalValueSize(getDataTypeSize(logChannel, i));
value = recordValue.toString();
int valueLength = value.length();
try {
checkStringValue(value);
} catch (WrongCharacterException e) {
value = LoggerUtils.buildError(Flag.UNKNOWN_ERROR);
logger.error(e.getMessage());
}
if (valueLength > size) {
value = LoggerUtils.buildError(Flag.UNKNOWN_ERROR);
logger.error("The string is too big, length is " + valueLength
+ " but max. length allowed is " + size + ", ChannelId: " + channelID);
}
break;
case BYTE:
value = String.format("0x%02x", recordValue.asByte());
break;
default:
throw new RuntimeException("unsupported valueType");
}
}
}
else {
// write errorflag
value = LoggerUtils.buildError(record.getFlag());
size = checkMinimalValueSize(getDataTypeSize(logChannel, i));
}
if (isError32) {
logRecordContainer.set(i, new LogRecordContainerAscii(channelID, recordBackup));
}
}
else {
// got no data
value = LoggerUtils.buildError(Flag.UNKNOWN_ERROR);
size = checkMinimalValueSize(getDataTypeSize(logChannel, i));
}
if (left) {
LoggerUtils.addSpaces(value, size, sb);
sb.append(value);
}
else {
sb.append(value);
LoggerUtils.addSpaces(value, size, sb);
}
if (LoggerUtils.hasNext(logRecordContainer, i)) {
sb.append(Const.SEPARATOR);
}
}
sb.append(Const.LINESEPARATOR); // All systems with the same newline charter
return sb.toString();
}
/**
* Checkes a string if it is IESData conform, e.g. wrong characters. If not it will drop a error.
*
* @param value
* the string value which should be checked
*/
private void checkStringValue(String value) throws WrongCharacterException {
if (value.contains(Const.SEPARATOR)) {
throw new WrongCharacterException(
"Wrong character: String contains Seperator character: " + Const.SEPARATOR);
}
else if (value.startsWith(Const.ERROR)) {
throw new WrongCharacterException("Wrong character: String begins with: " + Const.ERROR);
}
else if (value.startsWith(Const.HEXADECIMAL)) {
throw new WrongCharacterException("Wrong character: String begins with: " + Const.HEXADECIMAL);
}
else if (!value.matches("^[\\x00-\\x7F]*")) {
throw new WrongCharacterException("Wrong character: Non ASCII character in String.");
}
}
private int checkMinimalValueSize(int size) {
if (size < Const.VALUE_SIZE_MINIMAL) {
size = Const.VALUE_SIZE_MINIMAL;
}
return size;
}
/**
* Returns the PrintStream for logging.
*
* @param group
* @param loggingInterval
* @param date
* @param logChannelList
* @return the PrintStream for logging.
*/
private PrintStream getStream(LogIntervalContainerGroup group, int loggingInterval, int logTimeOffset,
Calendar calendar, HashMap<String, LogChannel> logChannelList) {
String filename = LoggerUtils.buildFilename(loggingInterval, logTimeOffset, calendar);
File file = new File(directoryPath + filename);
actualFile = file;
PrintStream out = null;
try {
if (file.exists()) {
out = new PrintStream(new FileOutputStream(file, true), false, Const.CHAR_SET);
}
else {
out = new PrintStream(new FileOutputStream(file, true), false, Const.CHAR_SET);
String headerString = LogFileHeader.getIESDataFormatHeaderString(group, file.getName(), loggingInterval,
logChannelList);
out.print(headerString);
out.flush();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return out;
}
/**
* Returns the size of a DataType / ValueType.
*
* @param logChannel
* @param iterator
* @return size of DataType / ValueType.
*/
private int getDataTypeSize(LogChannel logChannel, int iterator) {
int size = Const.VALUE_SIZE_MINIMAL;
if (logChannel != null) {
boolean isByteArray = logChannel.getValueType().equals(ValueType.BYTE_ARRAY);
boolean isString = logChannel.getValueType().equals(ValueType.STRING);
if (isString) {
// get length from channel for String / ByteArray
size = logChannel.getValueTypeLength();
}
else if (isByteArray) {
size = Const.HEXADECIMAL.length() + logChannel.getValueTypeLength() * 2;
}
else {
// get length from channel for simple value types
size = LoggerUtils.getLengthOfValueType(logChannel.getValueType());
}
}
else {
// get length from file
ValueType vt = LoggerUtils.identifyValueType(iterator + Const.NUM_OF_TIME_TYPES_IN_HEADER + 1, actualFile);
size = LoggerUtils.getLengthOfValueType(vt);
if ((vt.equals(ValueType.BYTE_ARRAY) || (vt.equals(ValueType.STRING)))
&& size <= Const.VALUE_SIZE_MINIMAL) {
size = LoggerUtils.getValueTypeLengthFromFile(iterator + Const.NUM_OF_TIME_TYPES_IN_HEADER + 1,
actualFile);
}
}
return size;
}
}