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.plugin.surefire.log.api.ConsoleLogger;
23 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
24
25 import javax.annotation.Nonnegative;
26 import javax.annotation.Nonnull;
27 import java.io.EOFException;
28 import java.io.File;
29 import java.io.IOException;
30 import java.nio.Buffer;
31 import java.nio.ByteBuffer;
32 import java.nio.CharBuffer;
33 import java.nio.channels.ReadableByteChannel;
34 import java.nio.charset.Charset;
35 import java.nio.charset.CharsetDecoder;
36 import java.nio.charset.CoderResult;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Map;
40
41 import static java.lang.Math.max;
42 import static java.lang.Math.min;
43 import static java.nio.charset.CodingErrorAction.REPLACE;
44 import static java.nio.charset.StandardCharsets.US_ASCII;
45 import static java.util.Arrays.copyOf;
46 import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
47 import static org.apache.maven.surefire.api.stream.AbstractStreamDecoder.StreamReadStatus.OVERFLOW;
48 import static org.apache.maven.surefire.api.stream.AbstractStreamDecoder.StreamReadStatus.UNDERFLOW;
49 import static org.apache.maven.surefire.shared.lang3.StringUtils.isBlank;
50
51
52
53
54
55
56 public abstract class AbstractStreamDecoder<M, MT extends Enum<MT>, ST extends Enum<ST>> implements AutoCloseable
57 {
58 public static final int BUFFER_SIZE = 1024;
59
60 private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
61
62 private static final String[] JVM_ERROR_PATTERNS = {
63 "could not create the java virtual machine", "error occurred during initialization",
64 "error:",
65 "could not reserve enough space", "could not allocate", "unable to allocate",
66 "java.lang.module.findexception"
67 };
68
69 private static final byte[] DEFAULT_STREAM_ENCODING_BYTES = DEFAULT_STREAM_ENCODING.name().getBytes( US_ASCII );
70
71 private static final int NO_POSITION = -1;
72 private static final int DELIMITER_LENGTH = 1;
73 private static final int BYTE_LENGTH = 1;
74 private static final int INT_LENGTH = 4;
75 private static final int LONG_LENGTH = 8;
76
77 private final ReadableByteChannel channel;
78 private final ForkNodeArguments arguments;
79 private final Map<Segment, MT> messageTypes;
80 private final ConsoleLogger logger;
81
82 protected AbstractStreamDecoder( @Nonnull ReadableByteChannel channel,
83 @Nonnull ForkNodeArguments arguments,
84 @Nonnull Map<Segment, MT> messageTypes )
85 {
86 this.channel = channel;
87 this.arguments = arguments;
88 this.messageTypes = messageTypes;
89 logger = arguments.getConsoleLogger();
90 }
91
92 public abstract M decode( @Nonnull Memento memento ) throws MalformedChannelException, IOException;
93
94 @Nonnull
95 protected abstract byte[] getEncodedMagicNumber();
96
97 @Nonnull
98 protected abstract ST[] nextSegmentType( @Nonnull MT messageType );
99
100 @Nonnull
101 protected abstract M toMessage( @Nonnull MT messageType, @Nonnull Memento memento )
102 throws MalformedFrameException;
103
104 @Nonnull
105 protected final ForkNodeArguments getArguments()
106 {
107 return arguments;
108 }
109
110 protected void debugStream( byte[] array, int position, int remaining )
111 {
112 }
113
114 protected MT readMessageType( @Nonnull Memento memento ) throws IOException, MalformedFrameException
115 {
116 byte[] header = getEncodedMagicNumber();
117 int readCount = DELIMITER_LENGTH + header.length + DELIMITER_LENGTH + BYTE_LENGTH + DELIMITER_LENGTH;
118 read( memento, readCount );
119 checkHeader( memento );
120 return messageTypes.get( readSegment( memento ) );
121 }
122
123 @Nonnull
124 @SuppressWarnings( "checkstyle:magicnumber" )
125 protected Segment readSegment( @Nonnull Memento memento ) throws IOException, MalformedFrameException
126 {
127 int readCount = readByte( memento ) & 0xff;
128 read( memento, readCount + DELIMITER_LENGTH );
129 ByteBuffer bb = memento.getByteBuffer();
130 Segment segment = new Segment( bb.array(), bb.arrayOffset() + ( (Buffer) bb ).position(), readCount );
131 ( (Buffer) bb ).position( ( (Buffer) bb ).position() + readCount );
132 checkDelimiter( memento );
133 return segment;
134 }
135
136 @Nonnull
137 @SuppressWarnings( "checkstyle:magicnumber" )
138 protected Charset readCharset( @Nonnull Memento memento ) throws IOException, MalformedFrameException
139 {
140 int length = readByte( memento ) & 0xff;
141 read( memento, length + DELIMITER_LENGTH );
142 ByteBuffer bb = memento.getByteBuffer();
143 byte[] array = bb.array();
144 int offset = bb.arrayOffset() + ( (Buffer) bb ).position();
145 ( (Buffer) bb ).position( ( (Buffer) bb ).position() + length );
146 boolean isDefaultEncoding = false;
147 if ( length == DEFAULT_STREAM_ENCODING_BYTES.length )
148 {
149 isDefaultEncoding = true;
150 for ( int i = 0; i < length; i++ )
151 {
152 isDefaultEncoding &= DEFAULT_STREAM_ENCODING_BYTES[i] == array[offset + i];
153 }
154 }
155
156 try
157 {
158 Charset charset =
159 isDefaultEncoding
160 ? DEFAULT_STREAM_ENCODING
161 : Charset.forName( new String( array, offset, length, US_ASCII ) );
162
163 checkDelimiter( memento );
164 return charset;
165 }
166 catch ( IllegalArgumentException e )
167 {
168 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(), ( (Buffer) bb ).position() );
169 }
170 }
171
172 protected String readString( @Nonnull Memento memento ) throws IOException, MalformedFrameException
173 {
174 ( (Buffer) memento.getCharBuffer() ).clear();
175 int readCount = readInt( memento );
176 if ( readCount < 0 )
177 {
178 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
179 ( (Buffer) memento.getByteBuffer() ).position() );
180 }
181 read( memento, readCount + DELIMITER_LENGTH );
182
183 final String string;
184 if ( readCount == 0 )
185 {
186 string = "";
187 }
188 else if ( readCount == 1 )
189 {
190 read( memento, 1 );
191 byte oneChar = memento.getByteBuffer().get();
192 string = oneChar == 0 ? null : String.valueOf( (char) oneChar );
193 }
194 else
195 {
196 string = readString( memento, readCount );
197 }
198 read( memento, 1 );
199 checkDelimiter( memento );
200 return string;
201 }
202
203 protected Integer readInteger( @Nonnull Memento memento ) throws IOException, MalformedFrameException
204 {
205 read( memento, BYTE_LENGTH );
206 boolean isNullObject = memento.getByteBuffer().get() == 0;
207 if ( isNullObject )
208 {
209 read( memento, DELIMITER_LENGTH );
210 checkDelimiter( memento );
211 return null;
212 }
213 return readInt( memento );
214 }
215
216 protected byte readByte( @Nonnull Memento memento ) throws IOException, MalformedFrameException
217 {
218 read( memento, BYTE_LENGTH + DELIMITER_LENGTH );
219 byte b = memento.getByteBuffer().get();
220 checkDelimiter( memento );
221 return b;
222 }
223
224 protected int readInt( @Nonnull Memento memento ) throws IOException, MalformedFrameException
225 {
226 read( memento, INT_LENGTH + DELIMITER_LENGTH );
227 int i = memento.getByteBuffer().getInt();
228 checkDelimiter( memento );
229 return i;
230 }
231
232 protected Long readLong( @Nonnull Memento memento ) throws IOException, MalformedFrameException
233 {
234 read( memento, BYTE_LENGTH );
235 boolean isNullObject = memento.getByteBuffer().get() == 0;
236 if ( isNullObject )
237 {
238 read( memento, DELIMITER_LENGTH );
239 checkDelimiter( memento );
240 return null;
241 }
242 return readLongPrivate( memento );
243 }
244
245 protected long readLongPrivate( @Nonnull Memento memento ) throws IOException, MalformedFrameException
246 {
247 read( memento, LONG_LENGTH + DELIMITER_LENGTH );
248 long num = memento.getByteBuffer().getLong();
249 checkDelimiter( memento );
250 return num;
251 }
252
253 @SuppressWarnings( "checkstyle:magicnumber" )
254 protected final void checkDelimiter( Memento memento ) throws MalformedFrameException
255 {
256 ByteBuffer bb = memento.bb;
257 if ( ( 0xff & bb.get() ) != ':' )
258 {
259 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(), ( (Buffer) bb ).position() );
260 }
261 }
262
263 protected final void checkHeader( Memento memento ) throws MalformedFrameException
264 {
265 ByteBuffer bb = memento.bb;
266
267 checkDelimiter( memento );
268
269 int shift = 0;
270 try
271 {
272 byte[] header = getEncodedMagicNumber();
273 byte[] bbArray = bb.array();
274 for ( int start = bb.arrayOffset() + ( (Buffer) bb ).position(), length = header.length;
275 shift < length; shift++ )
276 {
277 if ( bbArray[shift + start] != header[shift] )
278 {
279 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
280 ( (Buffer) bb ).position() + shift );
281 }
282 }
283 }
284 finally
285 {
286 ( (Buffer) bb ).position( ( (Buffer) bb ).position() + shift );
287 }
288
289 checkDelimiter( memento );
290 }
291
292 protected void checkArguments( Memento memento, int expectedDataElements )
293 throws MalformedFrameException
294 {
295 if ( memento.getData().size() != expectedDataElements )
296 {
297 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
298 ( (Buffer) memento.getByteBuffer() ).position() );
299 }
300 }
301
302 private String readString( @Nonnull final Memento memento, @Nonnegative final int totalBytes )
303 throws IOException, MalformedFrameException
304 {
305 memento.getDecoder().reset();
306 final CharBuffer output = memento.getCharBuffer();
307 ( (Buffer) output ).clear();
308 final ByteBuffer input = memento.getByteBuffer();
309 final List<String> strings = new ArrayList<>();
310 int countDecodedBytes = 0;
311 for ( boolean endOfInput = false; !endOfInput; )
312 {
313 final int bytesToRead = totalBytes - countDecodedBytes;
314 read( memento, bytesToRead );
315 int bytesToDecode = min( input.remaining(), bytesToRead );
316 final boolean isLastChunk = bytesToDecode == bytesToRead;
317 endOfInput = countDecodedBytes + bytesToDecode >= totalBytes;
318 do
319 {
320 boolean endOfChunk = output.remaining() >= bytesToRead;
321 boolean endOfOutput = isLastChunk && endOfChunk;
322 int readInputBytes = decodeString( memento.getDecoder(), input, output, bytesToDecode, endOfOutput,
323 memento.getLine().getPositionByteBuffer() );
324 bytesToDecode -= readInputBytes;
325 countDecodedBytes += readInputBytes;
326 }
327 while ( isLastChunk && bytesToDecode > 0 && output.hasRemaining() );
328
329 if ( isLastChunk || !output.hasRemaining() )
330 {
331 strings.add( ( (Buffer) output ).flip().toString() );
332 ( (Buffer) output ).clear();
333 }
334 }
335
336 memento.getDecoder().reset();
337 ( (Buffer) output ).clear();
338
339 return toString( strings );
340 }
341
342 private static int decodeString( @Nonnull CharsetDecoder decoder, @Nonnull ByteBuffer input,
343 @Nonnull CharBuffer output, @Nonnegative int bytesToDecode,
344 boolean endOfInput, @Nonnegative int errorStreamFrom )
345 throws MalformedFrameException
346 {
347 int limit = ( (Buffer) input ).limit();
348 ( (Buffer) input ).limit( ( (Buffer) input ).position() + bytesToDecode );
349
350 CoderResult result = decoder.decode( input, output, endOfInput );
351 if ( result.isError() || result.isMalformed() )
352 {
353 throw new MalformedFrameException( errorStreamFrom, ( (Buffer) input ).position() );
354 }
355
356 int decodedBytes = bytesToDecode - input.remaining();
357 ( (Buffer) input ).limit( limit );
358 return decodedBytes;
359 }
360
361 private static String toString( List<String> strings )
362 {
363 if ( strings.size() == 1 )
364 {
365 return strings.get( 0 );
366 }
367 StringBuilder concatenated = new StringBuilder( strings.size() * BUFFER_SIZE );
368 for ( String s : strings )
369 {
370 concatenated.append( s );
371 }
372 return concatenated.toString();
373 }
374
375 private void printCorruptedStream( Memento memento )
376 {
377 ByteBuffer bb = memento.getByteBuffer();
378 if ( bb.hasRemaining() )
379 {
380 int bytesToWrite = bb.remaining();
381 memento.getLine().write( bb, ( (Buffer) bb ).position(), bytesToWrite );
382 ( (Buffer) bb ).position( ( (Buffer) bb ).position() + bytesToWrite );
383 }
384 }
385
386
387
388
389
390
391 protected final void printRemainingStream( Memento memento )
392 {
393 printCorruptedStream( memento );
394 memento.getLine().printExistingLine();
395 memento.getLine().clear();
396 }
397
398
399
400
401 public static final class Segment
402 {
403 private final byte[] array;
404 private final int fromIndex;
405 private final int length;
406 private final int hashCode;
407
408 public Segment( byte[] array, int fromIndex, int length )
409 {
410 this.array = array;
411 this.fromIndex = fromIndex;
412 this.length = length;
413
414 int hashCode = 0;
415 int i = fromIndex;
416 for ( int loops = length >> 1; loops-- != 0; )
417 {
418 hashCode = 31 * hashCode + array[i++];
419 hashCode = 31 * hashCode + array[i++];
420 }
421 this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
422 }
423
424 @Override
425 public int hashCode()
426 {
427 return hashCode;
428 }
429
430 @Override
431 public boolean equals( Object obj )
432 {
433 if ( !( obj instanceof Segment ) )
434 {
435 return false;
436 }
437
438 Segment that = (Segment) obj;
439 if ( that.length != length )
440 {
441 return false;
442 }
443
444 for ( int i = 0; i < length; i++ )
445 {
446 if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
447 {
448 return false;
449 }
450 }
451 return true;
452 }
453 }
454
455 protected @Nonnull StreamReadStatus read( @Nonnull Memento memento, int recommendedCount ) throws IOException
456 {
457 ByteBuffer buffer = memento.getByteBuffer();
458 if ( buffer.remaining() >= recommendedCount && ( (Buffer) buffer ).limit() != 0 )
459 {
460 return OVERFLOW;
461 }
462 else
463 {
464 if ( ( (Buffer) buffer ).position() != 0
465 && recommendedCount > buffer.capacity() - ( (Buffer) buffer ).position() )
466 {
467 ( (Buffer) buffer.compact() ).flip();
468 memento.getLine().setPositionByteBuffer( 0 );
469 }
470 int mark = ( (Buffer) buffer ).position();
471 ( (Buffer) buffer ).position( ( (Buffer) buffer ).limit() );
472 ( (Buffer) buffer ).limit( min( ( (Buffer) buffer ).position() + recommendedCount, buffer.capacity() ) );
473 return read( buffer, mark, recommendedCount );
474 }
475 }
476
477 private StreamReadStatus read( ByteBuffer buffer, int oldPosition, int recommendedCount )
478 throws IOException
479 {
480 StreamReadStatus readStatus = null;
481 boolean isEnd = false;
482 try
483 {
484 while ( !isEnd && ( (Buffer) buffer ).position() - oldPosition < recommendedCount
485 && ( (Buffer) buffer ).position() < ( (Buffer) buffer ).limit() )
486 {
487 isEnd = channel.read( buffer ) == -1;
488 }
489 }
490 finally
491 {
492 ( (Buffer) buffer ).limit( ( (Buffer) buffer ).position() );
493 ( (Buffer) buffer ).position( oldPosition );
494 int readBytes = buffer.remaining();
495 boolean readComplete = readBytes >= recommendedCount;
496 if ( !isEnd || readComplete )
497 {
498 debugStream( buffer.array(),
499 buffer.arrayOffset() + ( (Buffer) buffer ).position(), buffer.remaining() );
500 readStatus = readComplete ? OVERFLOW : UNDERFLOW;
501 }
502 }
503
504 if ( readStatus == null )
505 {
506 throw new EOFException();
507 }
508 else
509 {
510 return readStatus;
511 }
512 }
513
514
515
516
517 public final class Memento
518 {
519 private CharsetDecoder currentDecoder;
520 private final CharsetDecoder defaultDecoder;
521 private final BufferedStream line = new BufferedStream( 32 );
522 private final List<Object> data = new ArrayList<>();
523 private final CharBuffer cb = CharBuffer.allocate( BUFFER_SIZE );
524 private final ByteBuffer bb = ByteBuffer.allocate( BUFFER_SIZE );
525
526 public Memento()
527 {
528 defaultDecoder = DEFAULT_STREAM_ENCODING.newDecoder()
529 .onMalformedInput( REPLACE )
530 .onUnmappableCharacter( REPLACE );
531 ( (Buffer) bb ).limit( 0 );
532 }
533
534 public void reset()
535 {
536 currentDecoder = null;
537 data.clear();
538 }
539
540 public CharsetDecoder getDecoder()
541 {
542 return currentDecoder == null ? defaultDecoder : currentDecoder;
543 }
544
545 public void setCharset( Charset charset )
546 {
547 if ( charset.name().equals( defaultDecoder.charset().name() ) )
548 {
549 currentDecoder = defaultDecoder;
550 }
551 else
552 {
553 currentDecoder = charset.newDecoder()
554 .onMalformedInput( REPLACE )
555 .onUnmappableCharacter( REPLACE );
556 }
557 }
558
559 public BufferedStream getLine()
560 {
561 return line;
562 }
563
564 public List<Object> getData()
565 {
566 return data;
567 }
568
569 public <T> T ofDataAt( int indexOfData )
570 {
571
572 return (T) data.get( indexOfData );
573 }
574
575 public CharBuffer getCharBuffer()
576 {
577 return cb;
578 }
579
580 public ByteBuffer getByteBuffer()
581 {
582 return bb;
583 }
584 }
585
586
587
588
589 public final class BufferedStream
590 {
591 private byte[] buffer;
592 private int count;
593 private int positionByteBuffer;
594 private boolean isNewLine;
595
596 BufferedStream( int capacity )
597 {
598 this.buffer = new byte[capacity];
599 }
600
601 public int getPositionByteBuffer()
602 {
603 return positionByteBuffer;
604 }
605
606 public void setPositionByteBuffer( int positionByteBuffer )
607 {
608 this.positionByteBuffer = positionByteBuffer;
609 }
610
611 public void write( ByteBuffer bb, int position, int length )
612 {
613 ensureCapacity( length );
614 byte[] array = bb.array();
615 int pos = bb.arrayOffset() + position;
616 while ( length-- > 0 )
617 {
618 positionByteBuffer++;
619 byte b = array[pos++];
620 if ( b == '\r' || b == '\n' )
621 {
622 if ( !isNewLine )
623 {
624 printExistingLine();
625 count = 0;
626 }
627 isNewLine = true;
628 }
629 else
630 {
631 buffer[count++] = b;
632 isNewLine = false;
633 }
634 }
635 }
636
637 public void clear()
638 {
639 count = 0;
640 }
641
642 @Override
643 public String toString()
644 {
645 return new String( buffer, 0, count, DEFAULT_STREAM_ENCODING );
646 }
647
648 private boolean isEmpty()
649 {
650 return count == 0;
651 }
652
653 private void ensureCapacity( int addCapacity )
654 {
655 int oldCapacity = buffer.length;
656 int exactCapacity = count + addCapacity;
657 if ( exactCapacity < 0 )
658 {
659 throw new OutOfMemoryError();
660 }
661
662 if ( oldCapacity < exactCapacity )
663 {
664 int newCapacity = oldCapacity << 1;
665 buffer = copyOf( buffer, max( newCapacity, exactCapacity ) );
666 }
667 }
668
669 void printExistingLine()
670 {
671 if ( isEmpty() )
672 {
673 return;
674 }
675
676 String s = toString();
677 if ( isBlank( s ) )
678 {
679 return;
680 }
681
682 if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
683 {
684 if ( logger.isDebugEnabled() )
685 {
686 logger.debug( s );
687 }
688 else if ( logger.isInfoEnabled() )
689 {
690 logger.info( s );
691 }
692 else
693 {
694
695 System.out.println( s );
696 }
697 }
698 else
699 {
700 if ( isJvmError( s ) )
701 {
702 logger.error( s );
703 }
704 else if ( logger.isDebugEnabled() )
705 {
706 logger.debug( s );
707 }
708
709 String msg = "Corrupted channel by directly writing to native stream in forked JVM "
710 + arguments.getForkChannelId() + ".";
711 File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
712 String dumpPath = dumpFile.getAbsolutePath();
713 arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpPath );
714 }
715 }
716
717 private boolean isJvmError( String line )
718 {
719 String lineLower = line.toLowerCase();
720 for ( String errorPattern : JVM_ERROR_PATTERNS )
721 {
722 if ( lineLower.contains( errorPattern ) )
723 {
724 return true;
725 }
726 }
727 return false;
728 }
729 }
730
731
732
733
734 public static final class MalformedFrameException extends Exception
735 {
736 private final int readFrom;
737 private final int readTo;
738
739 public MalformedFrameException( int readFrom, int readTo )
740 {
741 this.readFrom = readFrom;
742 this.readTo = readTo;
743 }
744
745 public int readFrom()
746 {
747 return readFrom;
748 }
749
750 public int readTo()
751 {
752 return readTo;
753 }
754
755 public boolean hasValidPositions()
756 {
757 return readFrom != NO_POSITION && readTo != NO_POSITION && readTo - readFrom > 0;
758 }
759 }
760
761
762
763
764
765
766
767
768 public enum StreamReadStatus
769 {
770 UNDERFLOW,
771 OVERFLOW,
772 EOF
773 }
774 }