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   * @param <E> type of the message
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         // one delimiter character ':' + <string> + one delimiter character ':' +
170         // one byte + one delimiter character ':' + <string> + one delimiter character ':'
171         int lengthOfMetadata = 1 + getEncodedMagicNumber().length + 1 + 1 + 1 + opcodeLength + 1;
172 
173         // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
174         lengthOfMetadata += 1 + 1 + (runMode == null ? 0 : runMode.getRunmodeBinary().length) + 1;
175 
176         if (encoder != null) {
177             // one byte of length + one delimiter character ':' + <string> + one delimiter character ':'
178             lengthOfMetadata += 1 + 1 + encoder.charset().name().length() + 1;
179         }
180 
181         // one byte (0x00 if NULL) + 4 bytes for integer + one delimiter character ':'
182         int lengthOfData = (1 + 4 + 1) * integersCounter;
183 
184         // one byte (0x00 if NULL) + 8 bytes for long + one delimiter character ':'
185         lengthOfData += (1 + 8 + 1) * longsCounter;
186 
187         for (String string : strings) {
188             String s = nonNull(string);
189             // 4 bytes of length of the string + one delimiter character ':' + <string> + one delimiter character ':'
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 }