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