View Javadoc
1   package org.apache.maven.surefire.booter.spi;
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.api.booter.Command;
23  import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
24  import org.apache.maven.surefire.api.booter.MasterProcessCommand;
25  import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
26  import org.apache.maven.surefire.api.util.internal.ImmutableMap;
27  
28  import javax.annotation.Nonnull;
29  import java.io.EOFException;
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.channels.ReadableByteChannel;
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  
38  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
39  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.MAGIC_NUMBER;
40  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
41  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
42  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
43  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
44  import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
45  
46  /**
47   * magic number : opcode [: opcode specific data]*
48   * <br>
49   *
50   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
51   * @since 3.0.0-M5
52   */
53  public class LegacyMasterProcessChannelDecoder implements MasterProcessChannelDecoder
54  {
55      private static final Map<String, MasterProcessCommand> COMMAND_OPCODES = stringsToOpcodes();
56  
57      private final ReadableByteChannel channel;
58  
59      public LegacyMasterProcessChannelDecoder( @Nonnull ReadableByteChannel channel )
60      {
61          this.channel = channel;
62      }
63  
64      @Override
65      @Nonnull
66      @SuppressWarnings( "checkstyle:innerassignment" )
67      public Command decode() throws IOException
68      {
69          List<String> tokens = new ArrayList<>( 3 );
70          StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
71          ByteBuffer buffer = ByteBuffer.allocate( 1 );
72  
73          start:
74          do
75          {
76              boolean endOfStream;
77              tokens.clear();
78              token.setLength( 0 );
79              FrameCompletion completion = null;
80              for ( boolean frameStarted = false; !( endOfStream = channel.read( buffer ) == -1 ) ; completion = null )
81              {
82                  buffer.flip();
83                  char c = (char) buffer.get();
84                  buffer.clear();
85  
86                  if ( !frameStarted )
87                  {
88                      if ( c == ':' )
89                      {
90                          frameStarted = true;
91                          token.setLength( 0 );
92                          tokens.clear();
93                      }
94                  }
95                  else
96                  {
97                      if ( c == ':' )
98                      {
99                          tokens.add( token.toString() );
100                         token.setLength( 0 );
101                         completion = frameCompleteness( tokens );
102                         if ( completion == FrameCompletion.COMPLETE )
103                         {
104                             break;
105                         }
106                         else if ( completion == FrameCompletion.MALFORMED )
107                         {
108                             DumpErrorSingleton.getSingleton()
109                                 .dumpStreamText( "Malformed frame with tokens " + tokens );
110                             continue start;
111                         }
112                     }
113                     else
114                     {
115                         token.append( c );
116                     }
117                 }
118             }
119 
120             if ( completion == FrameCompletion.COMPLETE )
121             {
122                 MasterProcessCommand cmd = COMMAND_OPCODES.get( tokens.get( 1 ) );
123                 if ( tokens.size() == 2 )
124                 {
125                     return new Command( cmd );
126                 }
127                 else if ( tokens.size() == 3 )
128                 {
129                     return new Command( cmd, tokens.get( 2 ) );
130                 }
131             }
132 
133             if ( endOfStream )
134             {
135                 throw new EOFException();
136             }
137         }
138         while ( true );
139     }
140 
141     private static FrameCompletion frameCompleteness( List<String> tokens )
142     {
143         if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
144         {
145             return FrameCompletion.MALFORMED;
146         }
147 
148         if ( tokens.size() >= 2 )
149         {
150             String opcode = tokens.get( 1 );
151             MasterProcessCommand cmd = COMMAND_OPCODES.get( opcode );
152             if ( cmd == null )
153             {
154                 return FrameCompletion.MALFORMED;
155             }
156             else if ( cmd.hasDataType() == ( tokens.size() == 3 ) )
157             {
158                 return FrameCompletion.COMPLETE;
159             }
160         }
161         return FrameCompletion.NOT_COMPLETE;
162     }
163 
164     @Override
165     public void close()
166     {
167     }
168 
169     /**
170      * Determines whether the frame is complete or malformed.
171      */
172     private enum FrameCompletion
173     {
174         NOT_COMPLETE,
175         COMPLETE,
176         MALFORMED
177     }
178 
179     private static Map<String, MasterProcessCommand> stringsToOpcodes()
180     {
181         Map<String, MasterProcessCommand> opcodes = new HashMap<>();
182         opcodes.put( "run-testclass", RUN_CLASS );
183         opcodes.put( "testset-finished", TEST_SET_FINISHED );
184         opcodes.put( "skip-since-next-test", SKIP_SINCE_NEXT_TEST );
185         opcodes.put( "shutdown", SHUTDOWN );
186         opcodes.put( "noop", NOOP );
187         opcodes.put( "bye-ack", BYE_ACK );
188         return new ImmutableMap<>( opcodes );
189     }
190 }