1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.api.stream;
20
21 import javax.annotation.Nonnull;
22
23 import java.io.IOException;
24 import java.nio.Buffer;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.WritableByteChannel;
27 import java.nio.charset.Charset;
28 import java.nio.charset.CharsetEncoder;
29
30 import org.apache.maven.surefire.api.report.RunMode;
31 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
32
33 import static java.lang.Math.ceil;
34 import static java.nio.CharBuffer.wrap;
35
36
37
38
39
40
41 public abstract class AbstractStreamEncoder<E extends Enum<E>> {
42 private static final byte BOOLEAN_NON_NULL_OBJECT = (byte) 0xff;
43 private static final byte BOOLEAN_NULL_OBJECT = (byte) 0;
44 private static final byte[] INT_BINARY = new byte[] {0, 0, 0, 0};
45
46 private final WritableByteChannel out;
47
48 public AbstractStreamEncoder(WritableByteChannel out) {
49 this.out = out;
50 }
51
52 @Nonnull
53 protected abstract byte[] getEncodedMagicNumber();
54
55 @Nonnull
56 protected abstract byte[] enumToByteArray(E e);
57
58 @Nonnull
59 protected abstract byte[] getEncodedCharsetName();
60
61 @Nonnull
62 protected abstract Charset getCharset();
63
64 @Nonnull
65 protected abstract CharsetEncoder newCharsetEncoder();
66
67 protected void write(ByteBuffer frame, boolean sendImmediately) throws IOException {
68 if (!sendImmediately && out instanceof WritableBufferedByteChannel) {
69 ((WritableBufferedByteChannel) out).writeBuffered(frame);
70 } else {
71 out.write(frame);
72 }
73 }
74
75 public void encodeHeader(ByteBuffer result, E operation, RunMode runMode, Long testRunId) {
76 encodeHeader(result, operation);
77
78 byte[] runmode = runMode == null ? new byte[0] : runMode.getRunmodeBinary();
79 result.put((byte) runmode.length);
80 result.put((byte) ':');
81 result.put(runmode);
82 result.put((byte) ':');
83
84 result.put((byte) (testRunId == null ? 0 : 1));
85 if (testRunId != null) {
86 result.putLong(testRunId);
87 }
88 result.put((byte) ':');
89 }
90
91 public void encodeHeader(ByteBuffer result, E operation) {
92 result.put((byte) ':');
93 result.put(getEncodedMagicNumber());
94 result.put((byte) ':');
95 byte[] opcode = enumToByteArray(operation);
96 result.put((byte) opcode.length);
97 result.put((byte) ':');
98 result.put(opcode);
99 result.put((byte) ':');
100 }
101
102 public void encodeCharset(ByteBuffer result) {
103 byte[] charsetNameBinary = getEncodedCharsetName();
104 result.put((byte) charsetNameBinary.length);
105 result.put((byte) ':');
106 result.put(charsetNameBinary);
107 result.put((byte) ':');
108 }
109
110 public void encodeString(CharsetEncoder encoder, ByteBuffer result, String string) {
111 String nonNullString = nonNull(string);
112
113 int counterPosition = ((Buffer) result).position();
114
115 result.put(INT_BINARY).put((byte) ':');
116
117 int msgStart = ((Buffer) result).position();
118 encoder.encode(wrap(nonNullString), result, true);
119 int msgEnd = ((Buffer) result).position();
120 int encodedMsgSize = msgEnd - msgStart;
121 result.putInt(counterPosition, encodedMsgSize);
122
123 ((Buffer) result).position(msgEnd);
124
125 result.put((byte) ':');
126 }
127
128 public void encodeInteger(ByteBuffer result, Integer i) {
129 if (i == null) {
130 result.put(BOOLEAN_NULL_OBJECT);
131 } else {
132 result.put(BOOLEAN_NON_NULL_OBJECT).putInt(i);
133 }
134 result.put((byte) ':');
135 }
136
137 public void encode(
138 CharsetEncoder encoder,
139 ByteBuffer result,
140 E operation,
141 RunMode runMode,
142 Long testRunId,
143 String... messages) {
144 encodeHeader(result, operation, runMode, testRunId);
145 encodeStringData(result, encoder, messages);
146 }
147
148 public void encode(CharsetEncoder encoder, ByteBuffer result, E operation, String... messages) {
149 encodeHeader(result, operation);
150 encodeStringData(result, encoder, messages);
151 }
152
153 private void encodeStringData(ByteBuffer result, CharsetEncoder encoder, String... messages) {
154 encodeCharset(result);
155 for (String message : messages) {
156 encodeString(encoder, result, message);
157 }
158 }
159
160 public int estimateBufferLength(
161 int opcodeLength,
162 RunMode runMode,
163 CharsetEncoder encoder,
164 int integersCounter,
165 int longsCounter,
166 String... strings) {
167 assert !(encoder == null && strings.length != 0);
168
169
170
171 int lengthOfMetadata = 1 + getEncodedMagicNumber().length + 1 + 1 + 1 + opcodeLength + 1;
172
173
174 lengthOfMetadata += 1 + 1 + (runMode == null ? 0 : runMode.getRunmodeBinary().length) + 1;
175
176 if (encoder != null) {
177
178 lengthOfMetadata += 1 + 1 + encoder.charset().name().length() + 1;
179 }
180
181
182 int lengthOfData = (1 + 4 + 1) * integersCounter;
183
184
185 lengthOfData += (1 + 8 + 1) * longsCounter;
186
187 for (String string : strings) {
188 String s = nonNull(string);
189
190 lengthOfData += 4 + 1 + (int) ceil(encoder.maxBytesPerChar() * s.length()) + 1;
191 }
192
193 return lengthOfMetadata + lengthOfData;
194 }
195
196 private static String nonNull(String msg) {
197 return msg == null ? "\u0000" : msg;
198 }
199 }