gernotbelger@9469: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@9469: * Software engineering by gernotbelger@9469: * Björnsen Beratende Ingenieure GmbH gernotbelger@9469: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@9469: * gernotbelger@9469: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@9469: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@9469: * documentation coming with Dive4Elements River for details. gernotbelger@9469: */ gernotbelger@9469: package org.dive4elements.river.artifacts.sinfo.tkhstate; gernotbelger@9469: gernotbelger@9469: import java.io.File; gernotbelger@9481: import java.io.IOException; gernotbelger@9532: import java.util.ArrayList; gernotbelger@9469: import java.util.Calendar; gernotbelger@9532: import java.util.Collection; gernotbelger@9469: import java.util.Date; gernotbelger@9469: import java.util.HashMap; gernotbelger@9481: import java.util.List; gernotbelger@9469: import java.util.Map; gernotbelger@9532: import java.util.TimeZone; gernotbelger@9469: gernotbelger@9469: import org.dive4elements.river.artifacts.model.Calculation; gernotbelger@9469: import org.dive4elements.river.artifacts.model.DateRange; gernotbelger@9522: import org.dive4elements.river.artifacts.sinfo.tkhcalculation.BedQualityD50KmValueFinder; gernotbelger@9481: import org.dive4elements.river.artifacts.sinfo.tkhstate.TsvHelper.TsvReaderException; gernotbelger@9469: import org.dive4elements.river.model.River; gernotbelger@9469: gernotbelger@9469: /** gernotbelger@9469: * Represents the contents of the 'bedheights.properties' files. gernotbelger@9469: * gernotbelger@9469: * @author Gernot Belger gernotbelger@9469: */ gernotbelger@9469: public final class BedQualityD50TimeRangeConfig { gernotbelger@9469: gernotbelger@9481: private static final String CONFIG_FILE = "d50_sohlkorndurchmesser_%s.tsv"; gernotbelger@9469: gernotbelger@9532: private final Map cache = new HashMap<>(); gernotbelger@9469: gernotbelger@9469: private static BedQualityD50TimeRangeConfig INSTANCE = new BedQualityD50TimeRangeConfig(); gernotbelger@9469: gernotbelger@9532: private static class BedQualityParseException extends Exception { gernotbelger@9481: gernotbelger@9481: private static final long serialVersionUID = 1L; gernotbelger@9481: gernotbelger@9532: public BedQualityParseException(final String message, final Throwable cause) { gernotbelger@9522: super(message, cause); gernotbelger@9481: } gernotbelger@9481: } gernotbelger@9481: gernotbelger@9532: public static synchronized DateRange getDefaults(final River river, final int soundingYear, final Calculation problems) { gernotbelger@9469: return INSTANCE.getBedHeightDefaultsForRiver(river, soundingYear, problems); gernotbelger@9469: } gernotbelger@9469: gernotbelger@9532: private synchronized DateRange getBedHeightDefaultsForRiver(final River river, final int soundingYear, final Calculation problems) { gernotbelger@9481: gernotbelger@9532: final String rivername = river.getName(); gernotbelger@9481: gernotbelger@9532: final CalRange[] ranges = getRanges(rivername, problems); gernotbelger@9532: if (ranges == null) gernotbelger@9532: return null; gernotbelger@9532: gernotbelger@9532: for (final CalRange range : ranges) { gernotbelger@9532: if (range.isSoundingYearInRange(soundingYear)) gernotbelger@9532: return new DateRange(range.getStartTimeQuery(), range.getEndTimeQuery()); gernotbelger@9532: } gernotbelger@9532: gernotbelger@9532: // Message for admin, not translated gernotbelger@9532: final File file = TsvHelper.makeFile2(CONFIG_FILE, rivername); gernotbelger@9532: final String message = "Die angegebene d50-Sohlkorndurchmesser Konfigurationsdatei enthält keinen gültigen Bereich für das konfigurierte Peiljahr."; //$NON-NLS-1$ gernotbelger@9532: problems.addProblem("sinfo.bedqualityd50config.configfile.loaderror", file.getPath(), message); gernotbelger@9532: return null; gernotbelger@9532: } gernotbelger@9532: gernotbelger@9532: private CalRange[] getRanges(final String rivername, final Calculation problems) { gernotbelger@9532: gernotbelger@9532: if (this.cache.containsKey(rivername)) gernotbelger@9532: return this.cache.get(rivername); gernotbelger@9532: gernotbelger@9532: final CalRange[] ranges = loadRanges(rivername, problems); gernotbelger@9532: if (ranges == null) { gernotbelger@9532: /* do not cache so we always get the problem message again */ gernotbelger@9481: return null; gernotbelger@9469: } gernotbelger@9532: gernotbelger@9532: this.cache.put(rivername, ranges); gernotbelger@9532: return ranges; gernotbelger@9532: } gernotbelger@9532: gernotbelger@9532: private CalRange[] loadRanges(final String rivername, final Calculation problems) { gernotbelger@9532: final File file = TsvHelper.makeFile2(CONFIG_FILE, rivername); gernotbelger@9532: final File fileCheck = TsvHelper.checkFile(file); gernotbelger@9532: if (fileCheck == null) gernotbelger@9532: return new CalRange[] { new CalRange(null, null, null, null) }; // automatically dateRange min/max will be taken gernotbelger@9532: gernotbelger@9532: try { gernotbelger@9532: final List results = TsvHelper.readTsv(file, 4); gernotbelger@9532: gernotbelger@9532: final Collection ranges = new ArrayList<>(results.size()); gernotbelger@9532: gernotbelger@9559: for (final String[] line : results) { gernotbelger@9559: if (line != null && line.length == 4) gernotbelger@9559: ranges.add(new CalRange(parseInput(line[0]), parseInput(line[1]), parseInput(line[2]), parseInput(line[3]))); gernotbelger@9559: } gernotbelger@9532: return ranges.toArray(new CalRange[ranges.size()]); gernotbelger@9532: } gernotbelger@9532: catch (final TsvReaderException | IOException | BedQualityParseException e) { gernotbelger@9532: problems.addProblem("sinfo.bedqualityd50config.configfile.loaderror", file.getPath(), e.getLocalizedMessage()); gernotbelger@9532: return null; gernotbelger@9532: } gernotbelger@9469: } gernotbelger@9469: gernotbelger@9481: private static Integer parseInput(final String raw) throws BedQualityParseException { gernotbelger@9532: gernotbelger@9481: final String value = raw.trim(); gernotbelger@9469: if (value.toUpperCase().equals("MIN") || value.toUpperCase().equals("MAX")) gernotbelger@9469: return null; gernotbelger@9532: gernotbelger@9469: try { gernotbelger@9469: return Integer.valueOf(value); gernotbelger@9469: } gernotbelger@9469: catch (final NumberFormatException e) { gernotbelger@9522: throw new BedQualityParseException("Invalid input; should be year ('yyyy') or 'MIN' or 'MAX'", e); gernotbelger@9469: } gernotbelger@9469: } gernotbelger@9469: gernotbelger@9522: private static final class CalRange { gernotbelger@9469: private final long startTimeSounding; gernotbelger@9469: private final long endTimeSounding; gernotbelger@9469: gernotbelger@9469: private final long startTimeQuery; gernotbelger@9469: private final long endTimeQuery; gernotbelger@9469: gernotbelger@9522: public Date getStartTimeQuery() { gernotbelger@9469: return new Date(this.startTimeQuery); gernotbelger@9469: } gernotbelger@9469: gernotbelger@9522: public Date getEndTimeQuery() { gernotbelger@9469: return new Date(this.endTimeQuery); gernotbelger@9469: } gernotbelger@9469: gernotbelger@9522: public CalRange(final Integer startYearSounding, final Integer endYearSounding, final Integer startYearQuery, final Integer endYearQuery) { gernotbelger@9469: gernotbelger@9522: this.startTimeSounding = (startYearSounding != null) ? getLongValForYear(startYearSounding, 0, 1) : BedQualityD50KmValueFinder.MIN_DATE.getTime(); gernotbelger@9522: this.startTimeQuery = (startYearQuery != null) ? getLongValForYear(startYearQuery, 0, 1) : BedQualityD50KmValueFinder.MIN_DATE.getTime(); gernotbelger@9522: this.endTimeSounding = (endYearSounding != null) ? getLongValForYear(endYearSounding, 11, 31) : BedQualityD50KmValueFinder.MAX_DATE.getTime(); gernotbelger@9522: this.endTimeQuery = (endYearQuery != null) ? getLongValForYear(endYearQuery, 11, 31) : BedQualityD50KmValueFinder.MAX_DATE.getTime(); gernotbelger@9469: } gernotbelger@9469: gernotbelger@9532: private static long getLongValForYear(final int year, final int month0based, final int dayOfMonth) { gernotbelger@9532: final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")); gernotbelger@9469: cal.clear(); gernotbelger@9469: cal.set(year, month0based, dayOfMonth); gernotbelger@9469: gernotbelger@9469: return cal.getTimeInMillis(); gernotbelger@9469: } gernotbelger@9469: gernotbelger@9522: public boolean isSoundingYearInRange(final int soundingYear) { gernotbelger@9469: gernotbelger@9532: final long time = getLongValForYear(soundingYear, 5, 5); // random date in the middle of the year gernotbelger@9469: if (time > this.startTimeSounding && time < this.endTimeSounding) gernotbelger@9469: return true; gernotbelger@9469: gernotbelger@9469: return false; gernotbelger@9469: } gernotbelger@9469: } gernotbelger@9522: }