View Javadoc
1   package org.apache.maven.surefire.booter;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.DataInputStream;
23  import java.io.IOException;
24  import java.io.UnsupportedEncodingException;
25  import java.nio.charset.Charset;
26  
27  import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
28  import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
29  import static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
30  import static java.lang.String.format;
31  
32  /**
33   * Commands which are sent from plugin to the forked jvm.
34   * Support and methods related to the commands.
35   *
36   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
37   * @since 2.19
38   */
39  public enum MasterProcessCommand
40  {
41      RUN_CLASS( 0, String.class ),
42      TEST_SET_FINISHED( 1, Void.class ),
43      SKIP_SINCE_NEXT_TEST( 2, Void.class ),
44      SHUTDOWN( 3, String.class ),
45  
46      /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
47      NOOP( 4, Void.class ),
48      BYE_ACK( 5, Void.class );
49  
50      private static final Charset ASCII = Charset.forName( "US-ASCII" );
51  
52      private final int id;
53  
54      private final Class<?> dataType;
55  
56      MasterProcessCommand( int id, Class<?> dataType )
57      {
58          this.id = id;
59          this.dataType = requireNonNull( dataType, "dataType cannot be null" );
60      }
61  
62      public int getId()
63      {
64          return id;
65      }
66  
67      public Class<?> getDataType()
68      {
69          return dataType;
70      }
71  
72      public boolean hasDataType()
73      {
74          return dataType != Void.class;
75      }
76  
77      @SuppressWarnings( "checkstyle:magicnumber" )
78      public byte[] encode( String data )
79      {
80          if ( !hasDataType() )
81          {
82              throw new IllegalArgumentException( "cannot use data without data type" );
83          }
84  
85          if ( getDataType() != String.class )
86          {
87              throw new IllegalArgumentException( "Data type can be only " + String.class );
88          }
89  
90          byte[] dataBytes = fromDataType( data );
91          byte[] encoded = new byte[8 + dataBytes.length];
92          int command = getId();
93          int len = dataBytes.length;
94          setCommandAndDataLength( command, len, encoded );
95          System.arraycopy( dataBytes, 0, encoded, 8, dataBytes.length );
96          return encoded;
97      }
98  
99      @SuppressWarnings( "checkstyle:magicnumber" )
100     public byte[] encode()
101     {
102         if ( getDataType() != Void.class )
103         {
104             throw new IllegalArgumentException( "Data type can be only " + getDataType() );
105         }
106         byte[] encoded = new byte[8];
107         int command = getId();
108         setCommandAndDataLength( command, 0, encoded );
109         return encoded;
110     }
111 
112     public static Command decode( DataInputStream is )
113         throws IOException
114     {
115         MasterProcessCommand command = resolve( is.readInt() );
116         if ( command == null )
117         {
118             return null;
119         }
120         else
121         {
122             int dataLength = is.readInt();
123             if ( dataLength > 0 )
124             {
125                 byte[] buffer = new byte[ dataLength ];
126                 is.readFully( buffer );
127 
128                 if ( command.getDataType() == Void.class )
129                 {
130                     throw new IOException( format( "Command %s unexpectedly read Void data with length %d.",
131                                                    command, dataLength ) );
132                 }
133 
134                 String data = command.toDataTypeAsString( buffer );
135                 return new Command( command, data );
136             }
137             else
138             {
139                 return new Command( command );
140             }
141         }
142     }
143 
144     String toDataTypeAsString( byte... data )
145     {
146         try
147         {
148             switch ( this )
149             {
150                 case RUN_CLASS:
151                     return new String( data, FORK_STREAM_CHARSET_NAME );
152                 case SHUTDOWN:
153                     return new String( data, ASCII );
154                 default:
155                     return null;
156             }
157         }
158         catch ( UnsupportedEncodingException e )
159         {
160             throw new IllegalStateException( e );
161         }
162     }
163 
164     byte[] fromDataType( String data )
165     {
166         switch ( this )
167         {
168             case RUN_CLASS:
169                 return encodeStringForForkCommunication( data );
170             case SHUTDOWN:
171                 return data.getBytes( ASCII );
172             default:
173                 return new byte[0];
174         }
175     }
176 
177     static MasterProcessCommand resolve( int id )
178     {
179         for ( MasterProcessCommand command : values() )
180         {
181             if ( id == command.id )
182             {
183                 return command;
184             }
185         }
186         return null;
187     }
188 
189     @SuppressWarnings( "checkstyle:magicnumber" )
190     static void setCommandAndDataLength( int command, int dataLength, byte... encoded )
191     {
192         encoded[0] = (byte) ( command >>> 24 );
193         encoded[1] = (byte) ( command >>> 16 );
194         encoded[2] = (byte) ( command >>> 8 );
195         encoded[3] = (byte) command;
196         encoded[4] = (byte) ( dataLength >>> 24 );
197         encoded[5] = (byte) ( dataLength >>> 16 );
198         encoded[6] = (byte) ( dataLength >>> 8 );
199         encoded[7] = (byte) dataLength;
200     }
201 }