/*
 * 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.internal.state;

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.BiConsumer;

import com.github.robozonky.internal.test.DateUtil;

final class StateModifierImpl<T> implements StateModifier<T>,
        Runnable {

    private final Collection<BiConsumer<StateStorage, String>> actions = new ArrayList<>(0);
    private final InstanceStateImpl<T> instanceState;

    public StateModifierImpl(final InstanceStateImpl<T> instanceState, final boolean fresh) {
        this.instanceState = instanceState;
        if (fresh) { // first action is to reset the class-specific state
            actions.add(StateStorage::unsetValues);
        }
    }

    @Override
    public StateModifier<T> put(final String key, final String value) {
        actions.add((state, section) -> state.setValue(section, key, value));
        return this;
    }

    @Override
    public StateModifier<T> remove(final String key) {
        final BiConsumer<StateStorage, String> action = (state, section) -> state.unsetValue(section, key);
        actions.add(action);
        return this;
    }

    @Override
    public void run() {
        final String sectionName = instanceState.getSectionName();
        final StateStorage backend = instanceState.getStorage();
        actions.forEach(a -> a.accept(backend, sectionName));
        backend.setValue(sectionName, Constants.LAST_UPDATED_KEY.getValue(), DateUtil.zonedNow()
            .toOffsetDateTime()
            .toString());
        backend.store();
    }
}

@@ -794,16 +794,143 @@
 eUtil;%0A%0A
+/**%0A * Has to be thread-safe, as %7B@link #put(String, String)%7D and %7B@link #run()%7D may happen in parallel.%0A * %0A * @param %3CT%3E%0A */%0A
 final cl
@@ -1514,24 +1514,61 @@
 ng value) %7B%0A
+        synchronized (actions) %7B%0A    
         acti
@@ -1629,24 +1629,34 @@
 y, value));%0A
+        %7D%0A
         retu
@@ -1752,56 +1752,57 @@
     
-final BiConsumer%3CStateStorage, String%3E
+synchronized (actions) %7B%0A           
  action
- = 
+s.add(
 (sta
@@ -1847,16 +1847,17 @@
 on, key)
+)
 ;%0A      
@@ -1862,28 +1862,9 @@
     
-actions.add(action);
+%7D
 %0A   
@@ -2054,24 +2054,61 @@
 tStorage();%0A
+        synchronized (actions) %7B%0A    
         acti
@@ -2153,24 +2153,34 @@
 tionName));%0A
+        %7D%0A
         back

Synchronize access to actions to prevent CME (fixes #570)