View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * 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   *
40   * @param <E> type of the message
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         // one delimiter character ':' + <string> + one delimiter character ':' +
171         // one byte + one delimiter character ':' + <string> + one delimiter character ':'
172         int lengthOfMetadata = 1 + getEncodedMagicNumber().length + 1 + 1 + 1 + opcodeLength + 1;
173 
174         // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
175         lengthOfMetadata += 1 + 1 + (runMode == null ? 0 : runMode.getRunmodeBinary().length) + 1;
176 
177         if (encoder != null) {
178             // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
179             lengthOfMetadata += 1 + 1 + encoder.charset().name().length() + 1;
180         }
181 
182         // one byte (0x00 if NULL) + 4 bytes for integer + one delimiter character ':'
183         int lengthOfData = (1 + 4 + 1) * integersCounter;
184 
185         // one byte (0x00 if NULL) + 8 bytes for long + one delimiter character ':'
186         lengthOfData += (1 + 8 + 1) * longsCounter;
187 
188         for (String string : strings) {
189             String s = nonNull(string);
190             // 4 bytes of length of the string + one delimiter character ':' + <string> + one delimiter character ':'
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 }