001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.rng.core.source32;
018
019import java.util.Random;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.ObjectOutputStream;
023import java.io.ObjectStreamClass;
024import java.io.ObjectInputStream;
025import java.io.ByteArrayOutputStream;
026
027import org.apache.commons.rng.core.util.NumberFactory;
028
029import java.io.ByteArrayInputStream;
030
031/**
032 * A provider that uses the {@link Random#nextInt()} method of the JDK's
033 * {@link Random} class as the source of randomness.
034 *
035 * <p>
036 * <b>Caveat:</b> All the other calls will be redirected to the methods
037 * implemented within this library.
038 * </p>
039 *
040 * <p>
041 * The state of this source of randomness is saved and restored through
042 * the serialization of the {@link Random} instance.
043 * </p>
044 *
045 * @since 1.0
046 */
047public class JDKRandom extends IntProvider {
048    /** Delegate.  Cannot be "final" (to allow serialization). */
049    private Random delegate;
050
051    /**
052     * An <code>ObjectInputStream</code> that's restricted to deserialize
053     * only {@link java.util.Random} using look-ahead deserialization.
054     *
055     * <p>Adapted from o.a.c.io.serialization.ValidatingObjectInputStream.</p>
056     *
057     * @see <a href="http://www.ibm.com/developerworks/library/se-lookahead/">
058     *  IBM DeveloperWorks Article: Look-ahead Java deserialization</a>
059     */
060    private static class ValidatingObjectInputStream extends ObjectInputStream {
061        /**
062         * @param in Input stream
063         * @throws IOException Signals that an I/O exception has occurred.
064         */
065        ValidatingObjectInputStream(final InputStream in) throws IOException {
066            super(in);
067        }
068
069        /** {@inheritDoc} */
070        @Override
071        protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException,
072            ClassNotFoundException {
073            // For legacy reasons the Random class is serialized using only primitives
074            // even though modern implementations use AtomicLong.
075            // The only expected class is java.util.Random.
076            if (!Random.class.getName().equals(osc.getName())) {
077                throw new IllegalStateException("Stream does not contain java.util.Random: " + osc.getName());
078            }
079            return super.resolveClass(osc);
080        }
081    }
082
083    /**
084     * Creates an instance with the given seed.
085     *
086     * @param seed Initial seed.
087     */
088    public JDKRandom(Long seed) {
089        delegate = new Random(seed);
090    }
091
092    /**
093     * {@inheritDoc}
094     *
095     * @see Random#nextInt()
096     */
097    @Override
098    public int next() {
099        return delegate.nextInt();
100    }
101
102    /** {@inheritDoc} */
103    @Override
104    protected byte[] getStateInternal() {
105        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
106             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
107
108            // Serialize the "delegate".
109            oos.writeObject(delegate);
110
111            final byte[] state = bos.toByteArray();
112            final int stateSize = state.length; // To allow state recovery.
113            // Compose the size with the state
114            final byte[] sizeAndState = composeStateInternal(NumberFactory.makeByteArray(stateSize),
115                                                             state);
116            return composeStateInternal(sizeAndState,
117                                        super.getStateInternal());
118        } catch (IOException e) {
119            // Workaround checked exception.
120            throw new IllegalStateException(e);
121        }
122    }
123
124    /** {@inheritDoc} */
125    @Override
126    protected void setStateInternal(byte[] s) {
127        // First obtain the state size
128        final byte[][] s2 = splitStateInternal(s, 4);
129        final int stateSize = NumberFactory.makeInt(s2[0]);
130
131        // Second obtain the state
132        final byte[][] c = splitStateInternal(s2[1], stateSize);
133
134        // Use look-ahead deserialization to validate the state byte[] contains java.util.Random.
135        try (ByteArrayInputStream bis = new ByteArrayInputStream(c[0]);
136             ObjectInputStream ois = new ValidatingObjectInputStream(bis)) {
137
138            delegate = (Random) ois.readObject();
139        } catch (ClassNotFoundException | IOException e) {
140            // Workaround checked exception.
141            throw new IllegalStateException(e);
142        }
143
144        super.setStateInternal(c[1]);
145    }
146}