1 package org.apache.maven.surefire.booter;
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.surefire.util.internal.StringUtils;
23
24 import java.io.DataInputStream;
25 import java.io.EOFException;
26 import java.io.IOException;
27 import java.io.UnsupportedEncodingException;
28 import java.nio.charset.Charset;
29
30 import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
31 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
32 import static org.apache.maven.surefire.util.internal.StringUtils.requireNonNull;
33 import static java.lang.String.format;
34
35
36
37
38
39
40
41
42 public enum MasterProcessCommand
43 {
44 RUN_CLASS( 0, String.class ),
45 TEST_SET_FINISHED( 1, Void.class ),
46 SKIP_SINCE_NEXT_TEST( 2, Void.class ),
47 SHUTDOWN( 3, String.class ),
48
49
50 NOOP( 4, Void.class );
51
52 private static final Charset ASCII = Charset.forName( "ASCII" );
53
54 private final int id;
55
56 private final Class<?> dataType;
57
58 MasterProcessCommand( int id, Class<?> dataType )
59 {
60 this.id = id;
61 this.dataType = requireNonNull( dataType, "dataType cannot be null" );
62 }
63
64 public int getId()
65 {
66 return id;
67 }
68
69 public Class<?> getDataType()
70 {
71 return dataType;
72 }
73
74 public boolean hasDataType()
75 {
76 return dataType != Void.class;
77 }
78
79 @SuppressWarnings( "checkstyle:magicnumber" )
80 public byte[] encode( String data )
81 {
82 if ( !hasDataType() )
83 {
84 throw new IllegalArgumentException( "cannot use data without data type" );
85 }
86
87 if ( getDataType() != String.class )
88 {
89 throw new IllegalArgumentException( "Data type can be only " + String.class );
90 }
91
92 byte[] dataBytes = fromDataType( data );
93 byte[] encoded = new byte[8 + dataBytes.length];
94 int command = getId();
95 int len = dataBytes.length;
96 setCommandAndDataLength( command, len, encoded );
97 System.arraycopy( dataBytes, 0, encoded, 8, dataBytes.length );
98 return encoded;
99 }
100
101 @SuppressWarnings( "checkstyle:magicnumber" )
102 public byte[] encode()
103 {
104 if ( getDataType() != Void.class )
105 {
106 throw new IllegalArgumentException( "Data type can be only " + getDataType() );
107 }
108 byte[] encoded = new byte[8];
109 int command = getId();
110 setCommandAndDataLength( command, 0, encoded );
111 return encoded;
112 }
113
114 public static Command decode( DataInputStream is )
115 throws IOException
116 {
117 MasterProcessCommand command = resolve( is.readInt() );
118 if ( command == null )
119 {
120 return null;
121 }
122 else
123 {
124 int dataLength = is.readInt();
125 if ( dataLength > 0 )
126 {
127 byte[] buffer = new byte[dataLength];
128 int read = 0;
129 int total = 0;
130 do
131 {
132 total += read;
133 read = is.read( buffer, total, dataLength - total );
134 } while ( read > 0 );
135
136 if ( command.getDataType() == Void.class )
137 {
138
139 throw new IOException( format( "Command %s unexpectedly read Void data with length %d.",
140 command, dataLength ) );
141 }
142
143 if ( total != dataLength )
144 {
145 if ( read == -1 )
146 {
147 throw new EOFException( "stream closed" );
148 }
149
150 throw new IOException( format( "%s read %d out of %d bytes",
151 MasterProcessCommand.class, total, dataLength ) );
152 }
153
154 String data = command.toDataTypeAsString( buffer );
155 return new Command( command, data );
156 }
157 else
158 {
159 return new Command( command );
160 }
161 }
162 }
163
164 String toDataTypeAsString( byte... data )
165 {
166 try
167 {
168 switch ( this )
169 {
170 case RUN_CLASS:
171 return new String( data, FORK_STREAM_CHARSET_NAME );
172 case SHUTDOWN:
173 return StringUtils.decode( data, ASCII );
174 default:
175 return null;
176 }
177 }
178 catch ( UnsupportedEncodingException e )
179 {
180 throw new IllegalStateException( e );
181 }
182 }
183
184 byte[] fromDataType( String data )
185 {
186 switch ( this )
187 {
188 case RUN_CLASS:
189 return encodeStringForForkCommunication( data );
190 case SHUTDOWN:
191 return StringUtils.encode( data, ASCII );
192 default:
193 return new byte[0];
194 }
195 }
196
197 static MasterProcessCommand resolve( int id )
198 {
199 for ( MasterProcessCommand command : values() )
200 {
201 if ( id == command.id )
202 {
203 return command;
204 }
205 }
206 return null;
207 }
208
209 @SuppressWarnings( "checkstyle:magicnumber" )
210 static void setCommandAndDataLength( int command, int dataLength, byte... encoded )
211 {
212 encoded[0] = (byte) ( command >>> 24 );
213 encoded[1] = (byte) ( command >>> 16 );
214 encoded[2] = (byte) ( command >>> 8 );
215 encoded[3] = (byte) command;
216 encoded[4] = (byte) ( dataLength >>> 24 );
217 encoded[5] = (byte) ( dataLength >>> 16 );
218 encoded[6] = (byte) ( dataLength >>> 8 );
219 encoded[7] = (byte) dataLength;
220 }
221 }