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 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   * Commands which are sent from plugin to the forked jvm.
37   * Support and methods related to the commands.
38   *
39   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
40   * @since 2.19
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      /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */
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                     // must read entire sequence to get to the next command; cannot be above the loop
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 }