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