/* * 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.utils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.TreeMap; import org.openmuc.framework.data.Flag; import org.openmuc.framework.data.ValueType; import org.openmuc.framework.datalogger.ascii.LogFileHeader; import org.openmuc.framework.datalogger.spi.LogChannel; import org.openmuc.framework.datalogger.spi.LogRecordContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggerUtils { private static final Logger logger = LoggerFactory.getLogger(LoggerUtils.class); /** * Returns all filenames of the given time span defined by the two dates * * @param loggingInterval * logging interval * @param logTimeOffset * logging time offset * @param startTimestamp * start time stamp * @param endTimestamp * end time stamp * @return a list of strings with all files names */ public static List<String> getFilenames(int loggingInterval, int logTimeOffset, long startTimestamp, long endTimestamp) { Calendar calendarStart = new GregorianCalendar(Locale.getDefault()); calendarStart.setTimeInMillis(startTimestamp); Calendar calendarEnd = new GregorianCalendar(Locale.getDefault()); calendarEnd.setTimeInMillis(endTimestamp); // Rename timespanToFilenames.... // Filename YYYYMMDD_<LoggingInterval>.dat List<String> filenames = new ArrayList<>(); while (calendarStart.before(calendarEnd) || calendarStart.equals(calendarEnd)) { String filename = buildFilename(loggingInterval, logTimeOffset, calendarStart); filenames.add(filename); // set date to 00:00:00 of the next day calendarStart.add(Calendar.DAY_OF_MONTH, 1); calendarStart.set(Calendar.HOUR_OF_DAY, 0); calendarStart.set(Calendar.MINUTE, 0); calendarStart.set(Calendar.SECOND, 0); calendarStart.set(Calendar.MILLISECOND, 0); } return filenames; } /** * Returns the filename, with the help of the timestamp and the interval. * * @param loggingInterval * logging interval * @param logTimeOffset * logging time offset * @param timestamp * timestamp * @return a filename from timestamp (date) and interval */ public static String getFilename(int loggingInterval, int logTimeOffset, long timestamp) { Calendar calendar = new GregorianCalendar(Locale.getDefault()); calendar.setTimeInMillis(timestamp); return buildFilename(loggingInterval, logTimeOffset, calendar); } /** * Builds the Logfile name from logging interval, logging time offset and the date of the calendar * * @param loggingInterval * logging interval * @param logTimeOffset * logging time offset * @param calendar * Calendar for the time of the file name * @return logging file name */ public static String buildFilename(int loggingInterval, int logTimeOffset, Calendar calendar) { StringBuilder sb = new StringBuilder(); sb.append(String.format(Const.DATE_FORMAT, calendar)); sb.append(Const.TIME_SEPERATOR); sb.append(String.valueOf(loggingInterval)); if (logTimeOffset != 0) { sb.append(Const.TIME_SEPERATOR); sb.append(logTimeOffset); } sb.append(Const.EXTENSION); return sb.toString(); } /** * Builds the Logfile name from string interval_timeOffset and the date of the calendar * * @param interval_timeOffset * the IntervallTimeOffset * @param calendar * Calendar for the time of the file name * @return logfile name */ public static String buildFilename(String interval_timeOffset, Calendar calendar) { StringBuilder sb = new StringBuilder(); sb.append(String.format(Const.DATE_FORMAT, calendar)); sb.append(Const.TIME_SEPERATOR); sb.append(interval_timeOffset); sb.append(Const.EXTENSION); return sb.toString(); } /** * Checks if it has a next container entry. * * @param containers * a list with LogRecordContainer * @param i * the current possition of the list * @return true if it has a next container entry, if not else. */ public static boolean hasNext(List<LogRecordContainer> containers, int i) { boolean result = false; if (i <= containers.size() - 2) { result = true; } return result; } /** * This method rename all *.dat files with the date from today in directoryPath into a *.old0, *.old1, ... * * @param directoryPath * directory path * @param calendar * Calendar for the time of the file name */ public static void renameAllFilesToOld(String directoryPath, Calendar calendar) { String date = String.format(Const.DATE_FORMAT, calendar); File dir = new File(directoryPath); File[] files = dir.listFiles(); if (files != null && files.length > 0) { for (File file : files) { String currentName = file.getName(); if (currentName.startsWith(date) && currentName.endsWith(Const.EXTENSION)) { String newName = currentName.substring(0, currentName.length() - Const.EXTENSION.length()); newName += Const.EXTENSION_OLD; int j = 0; File fileWithNewName = new File(directoryPath + newName + j); while (fileWithNewName.exists()) { ++j; fileWithNewName = new File(directoryPath + newName + j); } if (!file.renameTo(fileWithNewName)) { logger.error("Could not rename file to " + newName); } } } } else { logger.error("No file found in " + directoryPath); } } /** * This method renames a singel <date>_<loggerInterval>_<loggerTimeOffset>.dat file into a *.old0, * *.old1, ... * * @param directoryPath * directory path * @param loggerInterval_loggerTimeOffset * logger interval with logger time offset as String separated with underline * @param calendar * calendar of the day */ public static void renameFileToOld(String directoryPath, String loggerInterval_loggerTimeOffset, Calendar calendar) { File file = new File(directoryPath + buildFilename(loggerInterval_loggerTimeOffset, calendar)); if (file.exists()) { String currentName = file.getName(); String newName = currentName.substring(0, currentName.length() - Const.EXTENSION.length()); newName += Const.EXTENSION_OLD; int j = 0; File fileWithNewName = new File(directoryPath + newName + j); while (fileWithNewName.exists()) { ++j; fileWithNewName = new File(directoryPath + newName + j); } if (!file.renameTo(fileWithNewName)) { logger.error("Could not rename file to " + newName); } } } /** * Returns the calendar from today with the first hour, minute, second and millisecond. * * @param today * the current calendar * @return the calendar from today with the first hour, minute, second and millisecond */ public static Calendar getCalendarTodayZero(Calendar today) { Calendar calendarZero = new GregorianCalendar(Locale.getDefault()); calendarZero.set(today.get(Calendar.YEAR), today.get(Calendar.MONTH), today.get(Calendar.DATE), 0, 0, 0); calendarZero.set(Calendar.MILLISECOND, 0); return calendarZero; } /** * This method adds a blank spaces to a StringBuilder object. * * @param value * String value to fill up * @param size * maximal allowed size * @param sb * StringBuilder object to add the spaces */ public static void addSpaces(String value, int size, StringBuilder sb) { int i = value.length(); while (i < size) { sb.append(' '); ++i; } } /** * This method adds a string value up with blank spaces from left to right. * * @param sb * StringBuilder in wich the spaces will appended * @param number * the number of spaces */ public static void appendSpaces(StringBuilder sb, int number) { for (int i = 0; i < number; ++i) { sb.append(' '); } } /** * Construct a error value with the flag. * * @param flag * the wished error flag * @return a string with the flag and the standard error prefix. */ public static String buildError(Flag flag) { return Const.ERROR + flag.getCode(); } /** * Get the column number by name. * * @param line * the line to search * @param name * the name to search in line * @return the column number as int. */ public static int getColumnNumberByName(String line, String name) { int channelColumn = -1; // erste Zeile ohne Kommentar finden dann den Spaltennamen suchen und dessen Possitionsnummer zurückgeben. if (!line.startsWith(Const.COMMENT_SIGN)) { String columns[] = line.split(Const.SEPARATOR); int i = 0; while (i < columns.length) { if (name.equals(columns[i])) { return i; } i++; } } return channelColumn; } /** * Get the column number by name, in comments. It searches the line by his self. The BufferdReader has to be on the * begin of the file. * * @param name * the name to search * @param br * the BufferedReader * @return column number as int, -1 if name not found * @throws IOException * throws IOException If an I/O error occurs */ public static int getCommentColumnNumberByName(String name, BufferedReader br) throws IOException, NullPointerException { String line = br.readLine(); while (line != null && line.startsWith(Const.COMMENT_SIGN)) { if (line.contains(name)) { String columns[] = line.split(Const.SEPARATOR); for (int i = 0; i < columns.length; i++) { if (name.equals(columns[i])) { return i; } } } try { line = br.readLine(); } catch (NullPointerException e) { return -1; } } return -1; } /** * Get the value which is coded in the comment * * @param col_no * the number of the channel * @param column * the column * @param br * a BufferedReader * @return the value of a column of a specific col_num * @throws IOException * If an I/O error occurs */ public static String getCommentValue(int col_no, int column, BufferedReader br) throws IOException { String line = br.readLine(); String columnName = String.format("%03d", col_no); while (line != null && line.startsWith(Const.COMMENT_SIGN)) { if (line.startsWith(Const.COMMENT_SIGN + columnName)) { String columns[] = line.split(Const.SEPARATOR); return columns[column]; } line = br.readLine(); } return null; } /** * Identifies the ValueType of a logger value on a specific col_no * * @param columnNumber * column number * @param dataFile * the logger data file * @return the ValueType from col_num x */ public static ValueType identifyValueType(int columnNumber, File dataFile) { String valueTypeWithSize = getValueTypeAsString(columnNumber, dataFile); String valueTypeWithSizeArray[] = valueTypeWithSize.split(Const.VALUETYPE_ENDSIGN); String valueType = valueTypeWithSizeArray[0].split(Const.VALUETYPE_SIZE_SEPARATOR)[0]; return ValueType.valueOf(valueType); } public static int getValueTypeLengthFromFile(int columnNumber, File dataFile) { String valueType = getValueTypeAsString(columnNumber, dataFile); return getByteStringLength(valueType); } private static String getValueTypeAsString(int columnNumber, File dataFile) { BufferedReader br = null; String value = ""; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(dataFile), Const.CHAR_SET)); int column = LoggerUtils.getCommentColumnNumberByName(Const.COMMENT_NAME, br); if (column != -1) { value = LoggerUtils.getCommentValue(columnNumber, column, br); value = value.split(Const.VALUETYPE_ENDSIGN)[0]; } else { throw new NoSuchElementException("No element with name \"" + Const.COMMENT_NAME + "\" found."); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchElementException e) { e.printStackTrace(); } finally { closeBufferdReader(br); } return value; } /** * Returns the predefined size of a ValueType. * * @param valueType * the type to get the predefined size * @return predefined size of a ValueType as int. */ public static int getLengthOfValueType(ValueType valueType) { int size; switch (valueType) { case DOUBLE: size = Const.VALUE_SIZE_DOUBLE; break; case FLOAT: size = Const.VALUE_SIZE_DOUBLE; break; case INTEGER: size = Const.VALUE_SIZE_INTEGER; break; case LONG: size = Const.VALUE_SIZE_LONG; break; case SHORT: size = Const.VALUE_SIZE_SHORT; break; case BYTE_ARRAY: size = Const.VALUE_SIZE_MINIMAL; break; case STRING: size = Const.VALUE_SIZE_MINIMAL; break; case BOOLEAN: case BYTE: default: size = Const.VALUE_SIZE_MINIMAL; break; } return size; } /** * Converts a byte array to an hexadecimal string * * @param byteArray * the byte array to convert * @return hexadecimal string */ public static String byteArrayToHexString(byte[] byteArray) { char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[byteArray.length * 2]; for (int j = 0; j < byteArray.length; j++) { int v = byteArray[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Constructs the timestamp for every log value into a StringBuilder. * * @param sb * the StringBuilder to add the logger timestamp * @param calendar * Calendar with the wished time */ public static void setLoggerTimestamps(StringBuilder sb, Calendar calendar) { double unixtimestamp_sec = calendar.getTimeInMillis() / 1000.0; // double for milliseconds, nanoseconds sb.append(String.format(Const.DATE_FORMAT, calendar)); sb.append(Const.SEPARATOR); sb.append(String.format(Const.TIME_FORMAT, calendar)); sb.append(Const.SEPARATOR); sb.append(String.format(Locale.ENGLISH, "%10.3f", unixtimestamp_sec)); sb.append(Const.SEPARATOR); } /** * Constructs the timestamp for every log value into a StringBuilder. * * @param sb * the StringBuilder to add the logger timestamp * @param unixTimeStamp * unix time stamp in ms */ public static void setLoggerTimestamps(StringBuilder sb, long unixTimeStamp) { Calendar calendar = new GregorianCalendar(Locale.getDefault()); calendar.setTimeInMillis(unixTimeStamp); double unixtimestamp_sec = unixTimeStamp / 1000.0; // double for milliseconds, nanoseconds sb.append(String.format(Const.DATE_FORMAT, calendar)); sb.append(Const.SEPARATOR); sb.append(String.format(Const.TIME_FORMAT, calendar)); sb.append(Const.SEPARATOR); sb.append(String.format(Locale.ENGLISH, "%10.3f", unixtimestamp_sec)); sb.append(Const.SEPARATOR); } public static String getHeaderFromFile(String filePath, String logInterval_logTimeOffset) { BufferedReader br; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath)), Const.CHAR_SET)); } catch (IOException e1) { return ""; } if (br != null) { StringBuilder sb = new StringBuilder(); try { String line = br.readLine(); if (line != null) { sb.append(line); while (line != null && line.startsWith(Const.COMMENT_SIGN)) { sb.append(Const.LINESEPARATOR); line = br.readLine(); sb.append(line); } } } catch (IOException e) { logger.error("Problems to handle file: " + filePath, e); } finally { try { br.close(); } catch (IOException e) { logger.error("Cannot close file: " + filePath, e); } } return sb.toString(); } else { return ""; } } /** * Returns a RandomAccessFile of the specified file. * * @param file * file get the RandomAccessFile * @param accesMode * access mode * @return the RandomAccessFile of the specified file */ public static RandomAccessFile getRandomAccessFile(File file, String accesMode) { RandomAccessFile raf = null; try { raf = new RandomAccessFile(file, accesMode); } catch (FileNotFoundException e) { logger.warn("Requested logfile: '" + file.getAbsolutePath() + "' not found."); // e.printStackTrace(); } return raf; } public static PrintWriter getPrintWriter(File file, boolean append) throws IOException { PrintWriter writer = null; try { writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, append), Const.CHAR_SET)); } catch (IOException e) { logger.error("Cannot open file: " + file.getAbsolutePath()); throw new IOException(e); } return writer; } public static Map<String, Boolean> areHeadersIdentical(String loggerDirectory, List<LogChannel> channels, Calendar calendar) { Map<String, Boolean> areHeadersIdentical = new TreeMap<>(); Map<String, List<LogChannel>> logChannelMap = new TreeMap<>(); String key = ""; for (LogChannel logChannel : channels) { if (logChannel.getLoggingTimeOffset() != 0) { key = logChannel.getLoggingInterval() + Const.TIME_SEPERATOR_STRING + logChannel.getLoggingTimeOffset(); } else { key = logChannel.getLoggingInterval().toString(); } if (!logChannelMap.containsKey(key)) { List<LogChannel> logChannelList = new ArrayList<>(); logChannelList.add(logChannel); logChannelMap.put(key, logChannelList); } else { logChannelMap.get(key).add(logChannel); } } List<LogChannel> logChannels; for (Entry<String, List<LogChannel>> entry : logChannelMap.entrySet()) { key = entry.getKey(); logChannels = entry.getValue(); String fileName = LoggerUtils.buildFilename(key, calendar); String headerGenerated = LogFileHeader.getIESDataFormatHeaderString(fileName, logChannels); String oldHeader = LoggerUtils.getHeaderFromFile(loggerDirectory + fileName, key) + Const.LINESEPARATOR; boolean isHeaderIdentical = headerGenerated.equals(oldHeader); areHeadersIdentical.put(key, isHeaderIdentical); } return areHeadersIdentical; } /** * * fills a AsciiLogg file up. * * @param out * the output stream to write on * @param unixTimeStamp * unix time stamp * @param loggingInterval * logging interval * @param lastLogLineLength * the length of the last logging line of the file * @param numberOfFillUpLines * the number to fill up lines * @param errorValues * the error value set in the line * @return returns the unix time stamp of the last filled up line * @throws IOException * If an I/O error occurs */ public static long fillUp(PrintWriter out, long unixTimeStamp, long loggingInterval, int lastLogLineLength, long numberOfFillUpLines, StringBuilder errorValues) throws IOException { StringBuilder line = new StringBuilder(); for (int i = 0; i < numberOfFillUpLines; ++i) { line.setLength(0); unixTimeStamp += loggingInterval; setLoggerTimestamps(line, unixTimeStamp); line.append(errorValues); line.append(Const.LINESEPARATOR); out.append(line.toString()); } return unixTimeStamp; } public static long getNumberOfFillUpLines(long lastUnixTimeStamp, long loggingInterval) { long numberOfFillUpLines = 0; long currentUnixTimeStamp = System.currentTimeMillis(); numberOfFillUpLines = (currentUnixTimeStamp - lastUnixTimeStamp) / loggingInterval; return numberOfFillUpLines; } /** * Returns the error value as a StringBuilder. * * @param lineArray * a ascii line as a array with error code * @return StringBuilder with appended error */ public static StringBuilder getErrorValues(String lineArray[]) { StringBuilder errorValues = new StringBuilder(); int arrayLength = lineArray.length; int errorCodeLength = Const.ERROR.length() + 2; int separatorLength = Const.SEPARATOR.length(); int length = 0; for (int i = 3; i < arrayLength; ++i) { length = lineArray[i].length(); length -= errorCodeLength; if (i > arrayLength - 1) { length -= separatorLength; } appendSpaces(errorValues, length); errorValues.append(Const.ERROR); errorValues.append(Flag.DATA_LOGGING_NOT_ACTIVE.getCode()); if (i < arrayLength - 1) { errorValues.append(Const.SEPARATOR); } } return errorValues; } // private static String completeLastLine(String firstLogLine, String lastLogLine) { // // // TODO different size of logging lines, probably the last one is corrupted we have to fill it up // // TODO: wenn letzte Zeile zu defekt ist also ohne Timestamp, löschen und vorletzte Zeile nehmen und // // dessen Zeit. // int firstLogLineLength = firstLogLine.length(); // int lastLogLineLength = lastLogLine.length(); // // return ""; // } /** * Get the length from a type+length tuple. Example: "Byte_String,95" * * @param string * has to be a string with ByteType and length. * @param dataFile * the logger data file * @return the length of a ByteString in int. */ private static int getByteStringLength(String string) { String stringArray[]; int size; stringArray = string.split(Const.VALUETYPE_SIZE_SEPARATOR); try { size = Integer.parseInt(stringArray[1]); } catch (NumberFormatException e) { logger.warn("Not able to get ValueType length from String. Set length to minimal lenght " + Const.VALUE_SIZE_MINIMAL + "."); size = Const.VALUE_SIZE_MINIMAL; } return size; } private static void closeBufferdReader(BufferedReader br) { try { br.close(); } catch (Exception e1) { } } }