All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.robozonky.app.tenant.StatefulBoundedBalance Maven / Gradle / Ivy

/*
 * Copyright 2020 The RoboZonky Project
 *
 * 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 com.github.robozonky.app.tenant;

import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.github.robozonky.api.Money;
import com.github.robozonky.internal.Defaults;
import com.github.robozonky.internal.state.InstanceState;
import com.github.robozonky.internal.tenant.Tenant;
import com.github.robozonky.internal.test.DateUtil;

/**
 * On Zonky, we do not know the user's current account balance. However, in order for the robot to be able to function
 * effectively and not repeat useless Zonky calls etc., some information about balance is still required. This class
 * helps with that.
 * 

* By default, the balance is {@link Integer#MAX_VALUE}. Every time {@link #set(Money)} is called, the balance is set to * this new value. Also, a timer is reset - unless {@link #set(Money)} is called within reasonable time, the balance * as returned by {@link #get()} will be increased back to max. *

* The external code is expected to {@link #set(Money)} whatever value it thinks may still be available. For example, if * we attempt to invest 600 and Zonky gives us an insufficient balance error, we should {@link #set(Money)} 599 as that * is a value that can still be available. */ final class StatefulBoundedBalance { static final Money MAXIMUM = Money.from(Integer.MAX_VALUE); private static final Duration BALANCE_INCREASE_INTERVAL_STEP = Duration.ofMinutes(10); private static final String VALUE_KEY = "lastKnownUpperBound"; private static final Logger LOGGER = LogManager.getLogger(StatefulBoundedBalance.class); private final InstanceState state; private final AtomicReference currentValue = new AtomicReference<>(); private final AtomicReference lastModificationDate = new AtomicReference<>(); public StatefulBoundedBalance(final Tenant tenant) { this.state = tenant.getState(StatefulBoundedBalance.class); reloadFromState(); } private synchronized void reloadFromState() { var lastModified = state.getLastUpdated() .map(i -> i.atZoneSameInstant(Defaults.ZONKYCZ_ZONE_ID)) .orElseGet(() -> ZonedDateTime.ofInstant(Instant.EPOCH, Defaults.ZONKYCZ_ZONE_ID)); lastModificationDate.set(lastModified); var lastKnownValue = state.getValue(VALUE_KEY) .map(Money::from) .orElse(MAXIMUM); currentValue.set(lastKnownValue); LOGGER.trace("Loaded {} from {}", lastKnownValue, lastModified); } public synchronized Money set(final Money value) { var newValue = value.max(Money.from(1)); currentValue.set(newValue); lastModificationDate.set(DateUtil.zonedNow()); state.update(m -> m.put(VALUE_KEY, newValue.getValue() .toPlainString())); return newValue; } private Duration getTimeBetweenLastBalanceCheckAndNow() { var lastModified = lastModificationDate.get(); return Duration.between(DateUtil.zonedNow(), lastModified) .abs(); } public synchronized Money get() { var balance = currentValue.get(); if (balance.equals(MAXIMUM)) { // no need to do any other magic LOGGER.trace("Balance already at maximum."); return balance; } var lastModified = lastModificationDate.get(); var timeBetweenLastBalanceCheckAndNow = getTimeBetweenLastBalanceCheckAndNow(); if (timeBetweenLastBalanceCheckAndNow.compareTo(BALANCE_INCREASE_INTERVAL_STEP) < 0) { LOGGER.trace(() -> "Balance of " + balance + " is still fresh (" + DateUtil.toString(lastModified) + ")."); return balance; } LOGGER.trace(() -> "Resetting stale upper bound; too long since " + DateUtil.toString(lastModified) + "."); return set(MAXIMUM); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy