1 package org.apache.maven.plugin.surefire.extensions;
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.booter.Command;
24 import org.apache.maven.surefire.api.booter.MasterProcessCommand;
25 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
26 import org.apache.maven.surefire.extensions.CommandReader;
27 import org.apache.maven.surefire.api.util.internal.ImmutableMap;
28
29 import javax.annotation.Nonnull;
30 import java.io.IOException;
31 import java.nio.ByteBuffer;
32 import java.nio.channels.ClosedChannelException;
33 import java.nio.channels.NonWritableChannelException;
34 import java.nio.channels.WritableByteChannel;
35 import java.util.HashMap;
36 import java.util.Map;
37
38 import static java.nio.charset.StandardCharsets.US_ASCII;
39 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
40 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.MAGIC_NUMBER;
41 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
42 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
43 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
44 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
45 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public class StreamFeeder extends CloseableDaemonThread
61 {
62 private static final Map<MasterProcessCommand, String> COMMAND_OPCODES = opcodesToStrings();
63
64 private final WritableByteChannel channel;
65 private final CommandReader commandReader;
66 private final ConsoleLogger logger;
67
68 private volatile boolean disabled;
69 private volatile Throwable exception;
70
71 public StreamFeeder( @Nonnull String threadName, @Nonnull WritableByteChannel channel,
72 @Nonnull CommandReader commandReader, @Nonnull ConsoleLogger logger )
73 {
74 super( threadName );
75 this.channel = channel;
76 this.commandReader = commandReader;
77 this.logger = logger;
78 }
79
80 @Override
81 @SuppressWarnings( "checkstyle:innerassignment" )
82 public void run()
83 {
84 try ( WritableByteChannel c = channel )
85 {
86 for ( Command cmd; ( cmd = commandReader.readNextCommand() ) != null; )
87 {
88 if ( !disabled )
89 {
90 MasterProcessCommand cmdType = cmd.getCommandType();
91 byte[] data = cmdType.hasDataType() ? encode( cmdType, cmd.getData() ) : encode( cmdType );
92 c.write( ByteBuffer.wrap( data ) );
93 }
94 }
95 }
96 catch ( ClosedChannelException e )
97 {
98
99 }
100 catch ( IOException | NonWritableChannelException e )
101 {
102 exception = e.getCause() == null ? e : e.getCause();
103 }
104 catch ( IllegalArgumentException e )
105 {
106 logger.error( e.getLocalizedMessage() );
107 }
108 }
109
110 public void disable()
111 {
112 disabled = true;
113 }
114
115 public Throwable getException()
116 {
117 return exception;
118 }
119
120 @Override
121 public void close() throws IOException
122 {
123 channel.close();
124 }
125
126
127
128
129
130
131
132
133 public static byte[] encode( MasterProcessCommand cmdType, String data )
134 {
135 if ( !cmdType.hasDataType() )
136 {
137 throw new IllegalArgumentException( "cannot use data without data type" );
138 }
139
140 if ( cmdType.getDataType() != String.class )
141 {
142 throw new IllegalArgumentException( "Data type can be only " + String.class );
143 }
144
145 return encode( COMMAND_OPCODES.get( cmdType ), data )
146 .toString()
147 .getBytes( US_ASCII );
148 }
149
150
151
152
153
154
155
156 public static byte[] encode( MasterProcessCommand cmdType )
157 {
158 if ( cmdType.getDataType() != Void.class )
159 {
160 throw new IllegalArgumentException( "Data type can be only " + cmdType.getDataType() );
161 }
162
163 return encode( COMMAND_OPCODES.get( cmdType ), null )
164 .toString()
165 .getBytes( US_ASCII );
166 }
167
168
169
170
171
172
173
174
175 private static StringBuilder encode( String operation, String data )
176 {
177 StringBuilder s = new StringBuilder( 128 )
178 .append( ':' )
179 .append( MAGIC_NUMBER )
180 .append( ':' )
181 .append( operation );
182
183 if ( data != null )
184 {
185 s.append( ':' )
186 .append( data );
187 }
188
189 return s.append( ':' );
190 }
191
192 private static Map<MasterProcessCommand, String> opcodesToStrings()
193 {
194 Map<MasterProcessCommand, String> opcodes = new HashMap<>();
195 opcodes.put( RUN_CLASS, "run-testclass" );
196 opcodes.put( TEST_SET_FINISHED, "testset-finished" );
197 opcodes.put( SKIP_SINCE_NEXT_TEST, "skip-since-next-test" );
198 opcodes.put( SHUTDOWN, "shutdown" );
199 opcodes.put( NOOP, "noop" );
200 opcodes.put( BYE_ACK, "bye-ack" );
201 return new ImmutableMap<>( opcodes );
202 }
203 }