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  
25  import static java.lang.String.format;
26  import static java.nio.charset.StandardCharsets.ISO_8859_1;
27  import static java.nio.charset.StandardCharsets.US_ASCII;
28  import static java.util.Objects.requireNonNull;
29  import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
30  
31  /**
32   * Commands which are sent from plugin to the forked jvm.
33   * Support and methods related to the commands.
34   *
35   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
36   * @since 2.19
37   */
38  public enum MasterProcessCommand
39  {
40      RUN_CLASS( 0, String.class ),
41      TEST_SET_FINISHED( 1, Void.class ),
42      SKIP_SINCE_NEXT_TEST( 2, Void.class ),
43      SHUTDOWN( 3, String.class ),
44  
45      /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
46      NOOP( 4, Void.class ),
47      BYE_ACK( 5, Void.class );
48  
49      private final int id;
50  
51      private final Class<?> dataType;
52  
53      MasterProcessCommand( int id, Class<?> dataType )
54      {
55          this.id = id;
56          this.dataType = requireNonNull( dataType, "dataType cannot be null" );
57      }
58  
59      public int getId()
60      {
61          return id;
62      }
63  
64      public Class<?> getDataType()
65      {
66          return dataType;
67      }
68  
69      public boolean hasDataType()
70      {
71          return dataType != Void.class;
72      }
73  
74      @SuppressWarnings( "checkstyle:magicnumber" )
75      public byte[] encode( String data )
76      {
77          if ( !hasDataType() )
78          {
79              throw new IllegalArgumentException( "cannot use data without data type" );
80          }
81  
82          if ( getDataType() != String.class )
83          {
84              throw new IllegalArgumentException( "Data type can be only " + String.class );
85          }
86  
87          final byte[] dataBytes = fromDataType( data );
88          final int len = dataBytes.length;
89  
90          final byte[] encoded = new byte[8 + len];
91  
92          final int command = getId();
93          setCommandAndDataLength( command, len, encoded );
94          System.arraycopy( dataBytes, 0, encoded, 8, len );
95  
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         switch ( this )
147         {
148             case RUN_CLASS:
149                 return new String( data, ISO_8859_1 );
150             case SHUTDOWN:
151                 return new String( data, US_ASCII );
152             default:
153                 return null;
154         }
155     }
156 
157     byte[] fromDataType( String data )
158     {
159         switch ( this )
160         {
161             case RUN_CLASS:
162                 return encodeStringForForkCommunication( data );
163             case SHUTDOWN:
164                 return data.getBytes( US_ASCII );
165             default:
166                 return new byte[0];
167         }
168     }
169 
170     static MasterProcessCommand resolve( int id )
171     {
172         for ( MasterProcessCommand command : values() )
173         {
174             if ( id == command.id )
175             {
176                 return command;
177             }
178         }
179         return null;
180     }
181 
182     @SuppressWarnings( "checkstyle:magicnumber" )
183     static void setCommandAndDataLength( int command, int dataLength, byte... encoded )
184     {
185         encoded[0] = (byte) ( command >>> 24 );
186         encoded[1] = (byte) ( command >>> 16 );
187         encoded[2] = (byte) ( command >>> 8 );
188         encoded[3] = (byte) command;
189         encoded[4] = (byte) ( dataLength >>> 24 );
190         encoded[5] = (byte) ( dataLength >>> 16 );
191         encoded[6] = (byte) ( dataLength >>> 8 );
192         encoded[7] = (byte) dataLength;
193     }
194 }