1 package org.apache.maven.surefire.booter.spi;
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.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
48
49
50
51
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
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 }