View Javadoc
1   package org.apache.maven.surefire.api.stream;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * The base class of stream encoder.
38   * The type of message is expressed by opcode where the opcode object is described by the generic type {@link E}.
39   * @param <E> type of the message
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         // one delimiter character ':' + <string> + one delimiter character ':' +
181         // one byte + one delimiter character ':' + <string> + one delimiter character ':'
182         int lengthOfMetadata = 1 + getEncodedMagicNumber().length + 1 + 1 + 1 + opcodeLength + 1;
183 
184         if ( runMode != null )
185         {
186             // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
187             lengthOfMetadata += 1 + 1 + runMode.geRunmode().length() + 1;
188         }
189 
190         if ( encoder != null )
191         {
192             // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
193             lengthOfMetadata += 1 + 1 + encoder.charset().name().length() + 1;
194         }
195 
196         // one byte (0x00 if NULL) + 4 bytes for integer + one delimiter character ':'
197         int lengthOfData = ( 1 + 4 + 1 ) * integersCounter;
198 
199         // one byte (0x00 if NULL) + 8 bytes for long + one delimiter character ':'
200         lengthOfData += ( 1 + 8 + 1 ) * longsCounter;
201 
202         for ( String string : strings )
203         {
204             String s = nonNull( string );
205             // 4 bytes of length of the string + one delimiter character ':' + <string> + one delimiter character ':'
206             lengthOfData += 4 + 1 + (int) ceil( encoder.maxBytesPerChar() * s.length() ) + 1;
207         }
208 
209         return lengthOfMetadata + lengthOfData;
210     }
211 
212     private static String nonNull( String msg )
213     {
214         return msg == null ? "\u0000" : msg;
215     }
216 }