1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package jdk.incubator.http.internal.hpack;
24
25import java.nio.ByteBuffer;
26import java.util.*;
27import java.util.function.Consumer;
28import java.util.function.Function;
29import java.util.function.Supplier;
30
31import static java.nio.ByteBuffer.allocate;
32
33public final class BuffersTestingKit {
34
35    /**
36     * Relocates a {@code [position, limit)} region of the given buffer to
37     * corresponding region in a new buffer starting with provided {@code
38     * newPosition}.
39     *
40     * <p> Might be useful to make sure ByteBuffer's users do not rely on any
41     * absolute positions, but solely on what's reported by position(), limit().
42     *
43     * <p> The contents between the given buffer and the returned one are not
44     * shared.
45     */
46    public static ByteBuffer relocate(ByteBuffer buffer, int newPosition,
47                                      int newCapacity) {
48        int oldPosition = buffer.position();
49        int oldLimit = buffer.limit();
50
51        if (newPosition + oldLimit - oldPosition > newCapacity) {
52            throw new IllegalArgumentException();
53        }
54
55        ByteBuffer result;
56        if (buffer.isDirect()) {
57            result = ByteBuffer.allocateDirect(newCapacity);
58        } else {
59            result = allocate(newCapacity);
60        }
61
62        result.position(newPosition);
63        result.put(buffer).limit(result.position()).position(newPosition);
64        buffer.position(oldPosition);
65
66        if (buffer.isReadOnly()) {
67            return result.asReadOnlyBuffer();
68        }
69        return result;
70    }
71
72    public static Iterable<? extends ByteBuffer> relocateBuffers(
73            Iterable<? extends ByteBuffer> source) {
74        return () ->
75                new Iterator<ByteBuffer>() {
76
77                    private final Iterator<? extends ByteBuffer> it = source.iterator();
78
79                    @Override
80                    public boolean hasNext() {
81                        return it.hasNext();
82                    }
83
84                    @Override
85                    public ByteBuffer next() {
86                        ByteBuffer buf = it.next();
87                        int remaining = buf.remaining();
88                        int newCapacity = remaining + random.nextInt(17);
89                        int newPosition = random.nextInt(newCapacity - remaining + 1);
90                        return relocate(buf, newPosition, newCapacity);
91                    }
92                };
93    }
94
95    // TODO: not always of size 0 (it's fine for buffer to report !b.hasRemaining())
96    public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
97            Iterable<? extends ByteBuffer> source) {
98        return injectEmptyBuffers(source, () -> allocate(0));
99    }
100
101    public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
102            Iterable<? extends ByteBuffer> source,
103            Supplier<? extends ByteBuffer> emptyBufferFactory) {
104
105        return () ->
106                new Iterator<ByteBuffer>() {
107
108                    private final Iterator<? extends ByteBuffer> it = source.iterator();
109                    private ByteBuffer next = calculateNext();
110
111                    private ByteBuffer calculateNext() {
112                        if (random.nextBoolean()) {
113                            return emptyBufferFactory.get();
114                        } else if (it.hasNext()) {
115                            return it.next();
116                        } else {
117                            return null;
118                        }
119                    }
120
121                    @Override
122                    public boolean hasNext() {
123                        return next != null;
124                    }
125
126                    @Override
127                    public ByteBuffer next() {
128                        if (!hasNext()) {
129                            throw new NoSuchElementException();
130                        }
131                        ByteBuffer next = this.next;
132                        this.next = calculateNext();
133                        return next;
134                    }
135                };
136    }
137
138    public static ByteBuffer concat(Iterable<? extends ByteBuffer> split) {
139        return concat(split, ByteBuffer::allocate);
140    }
141
142    public static ByteBuffer concat(Iterable<? extends ByteBuffer> split,
143                                    Function<? super Integer, ? extends ByteBuffer> concatBufferFactory) {
144        int size = 0;
145        for (ByteBuffer bb : split) {
146            size += bb.remaining();
147        }
148
149        ByteBuffer result = concatBufferFactory.apply(size);
150        for (ByteBuffer bb : split) {
151            result.put(bb);
152        }
153
154        result.flip();
155        return result;
156    }
157
158    public static void forEachSplit(ByteBuffer bb,
159                                    Consumer<? super Iterable<? extends ByteBuffer>> action) {
160        forEachSplit(bb.remaining(),
161                (lengths) -> {
162                    int end = bb.position();
163                    List<ByteBuffer> buffers = new LinkedList<>();
164                    for (int len : lengths) {
165                        ByteBuffer d = bb.duplicate();
166                        d.position(end);
167                        d.limit(end + len);
168                        end += len;
169                        buffers.add(d);
170                    }
171                    action.accept(buffers);
172                });
173    }
174
175    private static void forEachSplit(int n, Consumer<? super Iterable<? extends Integer>> action) {
176        forEachSplit(n, new Stack<>(), action);
177    }
178
179    private static void forEachSplit(int n, Stack<Integer> path,
180                                     Consumer<? super Iterable<? extends Integer>> action) {
181        if (n == 0) {
182            action.accept(path);
183        } else {
184            for (int i = 1; i <= n; i++) {
185                path.push(i);
186                forEachSplit(n - i, path, action);
187                path.pop();
188            }
189        }
190    }
191
192    private static final Random random = new Random();
193
194    private BuffersTestingKit() {
195        throw new InternalError();
196    }
197
198//    public static void main(String[] args) {
199//
200//        List<ByteBuffer> buffers = Arrays.asList(
201//                (ByteBuffer) allocate(3).position(1).limit(2),
202//                allocate(0),
203//                allocate(7));
204//
205//        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
206//        List<ByteBuffer> result = new ArrayList<>();
207//        buf.forEach(result::add);
208//        System.out.println(result);
209//    }
210}
211