1 /* 2 ** Copyright 2011, The Android Open Source Project 3 ** 4 ** Licensed under the Apache License, Version 2.0 (the "License"); 5 ** you may not use this file except in compliance with the License. 6 ** You may obtain a copy of the License at 7 ** 8 ** http://www.apache.org/licenses/LICENSE-2.0 9 ** 10 ** Unless required by applicable law or agreed to in writing, software 11 ** distributed under the License is distributed on an "AS IS" BASIS, 12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 ** See the License for the specific language governing permissions and 14 ** limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.gldebugger; 18 19 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message; 20 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message.Function; 21 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message.Type; 22 import com.android.sdklib.util.SparseArray; 23 24 import java.io.DataInputStream; 25 import java.io.DataOutputStream; 26 import java.io.EOFException; 27 import java.io.FileInputStream; 28 import java.io.IOException; 29 import java.net.Socket; 30 import java.nio.ByteOrder; 31 import java.util.ArrayList; 32 33 abstract interface ProcessMessage { 34 abstract boolean processMessage(final MessageQueue queue, final Message msg) 35 throws IOException; 36 } 37 38 public class MessageQueue implements Runnable { 39 40 private boolean running = false; 41 private ByteOrder byteOrder; 42 private FileInputStream file; // if null, create and use socket 43 Thread thread = null; 44 private final ProcessMessage[] processes; 45 private ArrayList<Message> complete = new ArrayList<Message>(); // synchronized 46 private ArrayList<Message> commands = new ArrayList<Message>(); // synchronized 47 private SampleView sampleView; 48 49 public MessageQueue(SampleView sampleView, final ProcessMessage[] processes) { 50 this.sampleView = sampleView; 51 this.processes = processes; 52 } 53 54 public void start(final ByteOrder byteOrder, final FileInputStream file) { 55 if (running) 56 return; 57 running = true; 58 this.byteOrder = byteOrder; 59 this.file = file; 60 thread = new Thread(this); 61 thread.start(); 62 } 63 64 public void stop() { 65 if (!running) 66 return; 67 running = false; 68 } 69 70 public boolean isRunning() { 71 return running; 72 } 73 74 private void sendCommands(final int contextId) throws IOException { 75 synchronized (commands) { 76 for (int i = 0; i < commands.size(); i++) { 77 Message command = commands.get(i); 78 if (command.getContextId() == contextId || command.getContextId() == 0) { 79 sendMessage(commands.remove(i)); 80 i--; 81 } 82 } 83 } 84 } 85 86 public void addCommand(Message command) { 87 synchronized (commands) { 88 commands.add(command); 89 } 90 } 91 92 // these should only be accessed from the network thread; 93 // access call chain starts with run() 94 private DataInputStream dis = null; 95 private DataOutputStream dos = null; 96 private SparseArray<ArrayList<Message>> incoming = new SparseArray<ArrayList<Message>>(); 97 98 public void run() { 99 Socket socket = null; 100 if (file == null) 101 try { 102 socket = new Socket(); 103 socket.connect(new java.net.InetSocketAddress("127.0.0.1", Integer 104 .parseInt(sampleView.actionPort.getText()))); 105 dis = new DataInputStream(socket.getInputStream()); 106 dos = new DataOutputStream(socket.getOutputStream()); 107 } catch (Exception e) { 108 running = false; 109 Error(e); 110 } 111 else 112 dis = new DataInputStream(file); 113 114 while (running) { 115 try { 116 if (file != null && file.available() == 0) { 117 running = false; 118 break; 119 } 120 } catch (IOException e1) { 121 e1.printStackTrace(); 122 assert false; 123 } 124 125 Message msg = null; 126 if (incoming.size() > 0) { // find queued incoming 127 for (int i = 0; i < incoming.size(); i++) { 128 final ArrayList<Message> messages = incoming.valueAt(i); 129 if (messages.size() > 0) { 130 msg = messages.remove(0); 131 break; 132 } 133 } 134 } 135 try { 136 if (null == msg) // get incoming from network 137 msg = receiveMessage(dis); 138 processMessage(dos, msg); 139 } catch (IOException e) { 140 Error(e); 141 running = false; 142 break; 143 } 144 } 145 146 try { 147 if (socket != null) 148 socket.close(); 149 else 150 file.close(); 151 } catch (IOException e) { 152 Error(e); 153 running = false; 154 } 155 156 } 157 158 private void putMessage(final Message msg) { 159 ArrayList<Message> existing = incoming.get(msg.getContextId()); 160 if (existing == null) 161 incoming.put(msg.getContextId(), existing = new ArrayList<Message>()); 162 existing.add(msg); 163 } 164 165 Message receiveMessage(final int contextId) throws IOException { 166 Message msg = receiveMessage(dis); 167 while (msg.getContextId() != contextId) { 168 putMessage(msg); 169 msg = receiveMessage(dis); 170 } 171 return msg; 172 } 173 174 void sendMessage(final Message msg) throws IOException { 175 sendMessage(dos, msg); 176 } 177 178 // should only be used by DefaultProcessMessage 179 private SparseArray<Message> partials = new SparseArray<Message>(); 180 181 Message getPartialMessage(final int contextId) { 182 return partials.get(contextId); 183 } 184 185 // used to add BeforeCall to complete if it was skipped 186 void completePartialMessage(final int contextId) { 187 final Message msg = partials.get(contextId); 188 partials.remove(contextId); 189 assert msg != null; 190 assert msg.getType() == Type.BeforeCall; 191 if (msg != null) 192 synchronized (complete) { 193 complete.add(msg); 194 } 195 } 196 197 // can be used by other message processor as default processor 198 void defaultProcessMessage(final Message msg, boolean expectResponse, 199 boolean sendResponse) throws IOException { 200 final int contextId = msg.getContextId(); 201 if (msg.getType() == Type.BeforeCall) { 202 if (sendResponse) { 203 final Message.Builder builder = Message.newBuilder(); 204 builder.setContextId(contextId); 205 builder.setType(Type.Response); 206 builder.setExpectResponse(expectResponse); 207 builder.setFunction(Function.CONTINUE); 208 sendMessage(dos, builder.build()); 209 } 210 assert partials.indexOfKey(contextId) < 0; 211 partials.put(contextId, msg); 212 } else if (msg.getType() == Type.AfterCall) { 213 if (sendResponse) { 214 final Message.Builder builder = Message.newBuilder(); 215 builder.setContextId(contextId); 216 builder.setType(Type.Response); 217 builder.setExpectResponse(expectResponse); 218 builder.setFunction(Function.SKIP); 219 sendMessage(dos, builder.build()); 220 } 221 assert partials.indexOfKey(contextId) >= 0; 222 final Message before = partials.get(contextId); 223 partials.remove(contextId); 224 assert before.getFunction() == msg.getFunction(); 225 final Message completed = before.toBuilder().mergeFrom(msg) 226 .setType(Type.CompleteCall).build(); 227 synchronized (complete) { 228 complete.add(completed); 229 } 230 } else if (msg.getType() == Type.CompleteCall) { 231 // this type should only be encountered on client after processing 232 assert file != null; 233 assert !msg.getExpectResponse(); 234 assert !sendResponse; 235 assert partials.indexOfKey(contextId) < 0; 236 synchronized (complete) { 237 complete.add(msg); 238 } 239 } else if (msg.getType() == Type.Response && msg.getFunction() == Function.SETPROP) { 240 synchronized (complete) { 241 complete.add(msg); 242 } 243 } else 244 assert false; 245 } 246 247 public Message removeCompleteMessage(int contextId) { 248 synchronized (complete) { 249 if (complete.size() == 0) 250 return null; 251 if (0 == contextId) // get a message for any context 252 return complete.remove(0); 253 for (int i = 0; i < complete.size(); i++) { 254 Message msg = complete.get(i); 255 if (msg.getContextId() == contextId) { 256 complete.remove(i); 257 return msg; 258 } 259 } 260 } 261 return null; 262 } 263 264 private Message receiveMessage(final DataInputStream dis) 265 throws IOException { 266 int len = 0; 267 try { 268 len = dis.readInt(); 269 if (byteOrder == ByteOrder.LITTLE_ENDIAN) 270 len = Integer.reverseBytes(len); // readInt reads BIT_ENDIAN 271 } catch (EOFException e) { 272 Error(new Exception("EOF")); 273 } 274 byte[] buffer = new byte[len]; 275 int readLen = 0; 276 while (readLen < len) { 277 int read = -1; 278 try { 279 read = dis.read(buffer, readLen, len - readLen); 280 } catch (EOFException e) { 281 Error(new Exception("EOF")); 282 } 283 if (read < 0) { 284 Error(new Exception("read length = " + read)); 285 return null; 286 } else 287 readLen += read; 288 } 289 Message msg = Message.parseFrom(buffer); 290 sendCommands(msg.getContextId()); 291 return msg; 292 } 293 294 private void sendMessage(final DataOutputStream dos, final Message message) 295 throws IOException { 296 if (dos == null) 297 return; 298 assert message.getFunction() != Function.NEG; 299 final byte[] data = message.toByteArray(); 300 if (byteOrder == ByteOrder.BIG_ENDIAN) 301 dos.writeInt(data.length); 302 else 303 dos.writeInt(Integer.reverseBytes(data.length)); 304 dos.write(data); 305 } 306 307 private void processMessage(final DataOutputStream dos, final Message msg) throws IOException { 308 if (msg.getExpectResponse()) { 309 assert dos != null; // readonly source cannot expectResponse 310 for (ProcessMessage process : processes) 311 if (process.processMessage(this, msg)) 312 return; 313 defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse()); 314 } else 315 defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse()); 316 } 317 318 void Error(Exception e) { 319 sampleView.showError(e); 320 } 321 } 322