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 GLFramesView sampleView; 48 49 public MessageQueue(GLFramesView 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 @Override 99 public void run() { 100 Socket socket = null; 101 if (file == null) 102 try { 103 socket = new Socket(); 104 socket.connect(new java.net.InetSocketAddress("127.0.0.1", Integer 105 .parseInt(sampleView.actionPort.getText()))); 106 dis = new DataInputStream(socket.getInputStream()); 107 dos = new DataOutputStream(socket.getOutputStream()); 108 } catch (Exception e) { 109 running = false; 110 Error(e); 111 } 112 else 113 dis = new DataInputStream(file); 114 115 while (running) { 116 try { 117 if (file != null && file.available() == 0) { 118 running = false; 119 break; 120 } 121 } catch (IOException e1) { 122 e1.printStackTrace(); 123 assert false; 124 } 125 126 Message msg = null; 127 if (incoming.size() > 0) { // find queued incoming 128 for (int i = 0; i < incoming.size(); i++) { 129 final ArrayList<Message> messages = incoming.valueAt(i); 130 if (messages.size() > 0) { 131 msg = messages.remove(0); 132 break; 133 } 134 } 135 } 136 try { 137 if (null == msg) // get incoming from network 138 msg = receiveMessage(dis); 139 processMessage(dos, msg); 140 } catch (IOException e) { 141 Error(e); 142 running = false; 143 break; 144 } 145 } 146 147 try { 148 if (socket != null) 149 socket.close(); 150 else 151 file.close(); 152 } catch (IOException e) { 153 Error(e); 154 running = false; 155 } 156 157 } 158 159 private void putMessage(final Message msg) { 160 ArrayList<Message> existing = incoming.get(msg.getContextId()); 161 if (existing == null) 162 incoming.put(msg.getContextId(), existing = new ArrayList<Message>()); 163 existing.add(msg); 164 } 165 166 Message receiveMessage(final int contextId) throws IOException { 167 Message msg = receiveMessage(dis); 168 while (msg.getContextId() != contextId) { 169 putMessage(msg); 170 msg = receiveMessage(dis); 171 } 172 return msg; 173 } 174 175 void sendMessage(final Message msg) throws IOException { 176 sendMessage(dos, msg); 177 } 178 179 // should only be used by DefaultProcessMessage 180 private SparseArray<Message> partials = new SparseArray<Message>(); 181 182 Message getPartialMessage(final int contextId) { 183 return partials.get(contextId); 184 } 185 186 // used to add BeforeCall to complete if it was skipped 187 void completePartialMessage(final int contextId) { 188 final Message msg = partials.get(contextId); 189 partials.remove(contextId); 190 assert msg != null; 191 assert msg.getType() == Type.BeforeCall; 192 if (msg != null) 193 synchronized (complete) { 194 complete.add(msg); 195 } 196 } 197 198 // can be used by other message processor as default processor 199 void defaultProcessMessage(final Message msg, boolean expectResponse, 200 boolean sendResponse) throws IOException { 201 final int contextId = msg.getContextId(); 202 if (msg.getType() == Type.BeforeCall) { 203 if (sendResponse) { 204 final Message.Builder builder = Message.newBuilder(); 205 builder.setContextId(contextId); 206 builder.setType(Type.Response); 207 builder.setExpectResponse(expectResponse); 208 builder.setFunction(Function.CONTINUE); 209 sendMessage(dos, builder.build()); 210 } 211 assert partials.indexOfKey(contextId) < 0; 212 partials.put(contextId, msg); 213 } else if (msg.getType() == Type.AfterCall) { 214 if (sendResponse) { 215 final Message.Builder builder = Message.newBuilder(); 216 builder.setContextId(contextId); 217 builder.setType(Type.Response); 218 builder.setExpectResponse(expectResponse); 219 builder.setFunction(Function.SKIP); 220 sendMessage(dos, builder.build()); 221 } 222 assert partials.indexOfKey(contextId) >= 0; 223 final Message before = partials.get(contextId); 224 partials.remove(contextId); 225 assert before.getFunction() == msg.getFunction(); 226 final Message completed = before.toBuilder().mergeFrom(msg) 227 .setType(Type.CompleteCall).build(); 228 synchronized (complete) { 229 complete.add(completed); 230 } 231 } else if (msg.getType() == Type.CompleteCall) { 232 // this type should only be encountered on client after processing 233 assert file != null; 234 assert !msg.getExpectResponse(); 235 assert !sendResponse; 236 assert partials.indexOfKey(contextId) < 0; 237 synchronized (complete) { 238 complete.add(msg); 239 } 240 } else if (msg.getType() == Type.Response && msg.getFunction() == Function.SETPROP) { 241 synchronized (complete) { 242 complete.add(msg); 243 } 244 } else 245 assert false; 246 } 247 248 public Message removeCompleteMessage(int contextId) { 249 synchronized (complete) { 250 if (complete.size() == 0) 251 return null; 252 if (0 == contextId) // get a message for any context 253 return complete.remove(0); 254 for (int i = 0; i < complete.size(); i++) { 255 Message msg = complete.get(i); 256 if (msg.getContextId() == contextId) { 257 complete.remove(i); 258 return msg; 259 } 260 } 261 } 262 return null; 263 } 264 265 private Message receiveMessage(final DataInputStream dis) 266 throws IOException { 267 int len = 0; 268 try { 269 len = dis.readInt(); 270 if (byteOrder == ByteOrder.LITTLE_ENDIAN) 271 len = Integer.reverseBytes(len); // readInt reads BIT_ENDIAN 272 } catch (EOFException e) { 273 Error(new Exception("EOF")); 274 } 275 byte[] buffer = new byte[len]; 276 int readLen = 0; 277 while (readLen < len) { 278 int read = -1; 279 try { 280 read = dis.read(buffer, readLen, len - readLen); 281 } catch (EOFException e) { 282 Error(new Exception("EOF")); 283 } 284 if (read < 0) { 285 Error(new Exception("read length = " + read)); 286 return null; 287 } else 288 readLen += read; 289 } 290 Message msg = Message.parseFrom(buffer); 291 sendCommands(msg.getContextId()); 292 return msg; 293 } 294 295 private void sendMessage(final DataOutputStream dos, final Message message) 296 throws IOException { 297 if (dos == null) 298 return; 299 assert message.getFunction() != Function.NEG; 300 final byte[] data = message.toByteArray(); 301 if (byteOrder == ByteOrder.BIG_ENDIAN) 302 dos.writeInt(data.length); 303 else 304 dos.writeInt(Integer.reverseBytes(data.length)); 305 dos.write(data); 306 } 307 308 private void processMessage(final DataOutputStream dos, final Message msg) throws IOException { 309 if (msg.getExpectResponse()) { 310 assert dos != null; // readonly source cannot expectResponse 311 for (ProcessMessage process : processes) 312 if (process.processMessage(this, msg)) 313 return; 314 defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse()); 315 } else 316 defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse()); 317 } 318 319 void Error(Exception e) { 320 sampleView.showError(e); 321 } 322 } 323