/*
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 4652922
 *
 * @summary synopsis: ReliableLog.update should pad records to 4-byte
 * boundaries
 * @author Ann Wollrath
 *
 * @run main/othervm/timeout=240 LogTest
 */

import sun.rmi.log.LogHandler;
import sun.rmi.log.ReliableLog;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;

public class LogTest {

    private static int crashPoint = 0;
    private static boolean spansBoundary = false;
    private static ReliableLog log =  null;
    private static Counter counter = new Counter();

    public static void main(String[] args) throws Exception {

        System.err.println("\nRegression test for bug 4652922\n");

        System.setProperty("sun.rmi.log.class", MyLogFile.class.getName());
        //System.setProperty("sun.rmi.log.debug", "true");

        log = new ReliableLog(".", new TestLogHandler(counter), false);

        writeUpdatesCrashAndRecover(10, 1, false, 10);
        writeUpdatesCrashAndRecover(10, 1, true, 20);
        writeUpdatesCrashAndRecover(10, 2, true, 30);
        writeUpdatesCrashAndRecover(9, 2, false, 40);
        writeUpdatesCrashAndRecover(9, 3, true, 50);
        log.close();
    }

    private static void writeUpdatesCrashAndRecover(int updates,
                                                    int crashValue,
                                                    boolean spans,
                                                    int expectedCount)
        throws IOException
    {
        /*
         * Write updates
         */
        System.err.println("\nwrite updates: " + updates);
        for (int i = 0; i < updates; i++) {
            counter.increment();
            log.update(counter);
        }

        /*
         * Crash
         */
        crashPoint = crashValue;
        spansBoundary = spans;
        System.err.println("crash during next update on sync #" +
                           crashPoint +
                           " (spansBoundary = " + spansBoundary + ")");
        try {
            System.err.println(
                "write one update (update record should " +
                ((counter.value() + 1 == expectedCount) ? "" : "not ") +
                "be complete)");
            counter.increment();
            log.update(counter);
            throw new RuntimeException("failed to reach crashpoint " +
                                       crashPoint);
        } catch (IOException e) {
            System.err.println("caught IOException; message: " +
                               e.getMessage());
            log.close();
        }

        /*
         * Recover
         */
        log = new ReliableLog(".", new TestLogHandler(null), false);
        try {
            System.err.println("recover log");
            counter = (Counter) log.recover();
            System.err.println("recovered counter value: " + counter.value());
            if (counter.value() != expectedCount) {
                throw new RuntimeException("unexpected counter value " +
                                           counter.value());
            }
            System.err.println("log recovery successful");

        } catch (IOException e) {
            System.err.println("log should recover after crash point");
            e.printStackTrace();
            throw new RuntimeException(
                "log should recover after crash point");
        }

    }

    private static class Counter implements Serializable {
        private static long serialVersionUID = 1;
        private int count = 0;

        Counter() {}

        int increment() {
            return ++count;
        }

        int value() {
            return count;
        }

        void update(Counter value) {
            if (value.value() < count) {
                throw new IllegalStateException(
                    "bad update (count = " + count + ", value = " + value + ")");
            } else {
                count = value.value();
            }
        }
    }

    static class TestLogHandler extends LogHandler {

        private final Counter initialState;

        TestLogHandler(Counter initialState) {
            this.initialState = initialState;
        }

        public Object initialSnapshot() {
            if (initialState == null) {
                throw new IllegalStateException(
                    "attempting initialSnapshot with null");
            }
            return initialState;
        }

        public Object applyUpdate(Object update, Object state) {
            ((Counter) state).update((Counter) update);
            return state;
        }
    }

    public static class MyLogFile extends ReliableLog.LogFile {

        public MyLogFile(String name, String mode)
            throws FileNotFoundException, IOException
        {
            super(name, mode);
        }

        protected void sync() throws IOException {
            if (crashPoint != 0) {
                if (--crashPoint == 0) {
                    throw new IOException("crash point reached");
                }
            }
            super.sync();
        }

        protected boolean checkSpansBoundary(long fp) {
            return
                crashPoint > 0 ? spansBoundary : super.checkSpansBoundary(fp);
        }
    }
}
