/* ** Copyright 2011, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package com.android.glesv2debugger; import com.android.glesv2debugger.DebuggerMessage.Message; import com.android.glesv2debugger.DebuggerMessage.Message.Function; import com.android.glesv2debugger.DebuggerMessage.Message.Type; import com.android.sdklib.util.SparseArray; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; import java.nio.ByteOrder; import java.util.ArrayList; abstract interface ProcessMessage { abstract boolean processMessage(final MessageQueue queue, final Message msg) throws IOException; } public class MessageQueue implements Runnable { private boolean running = false; private ByteOrder byteOrder; private FileInputStream file; // if null, create and use socket Thread thread = null; private final ProcessMessage[] processes; private ArrayList<Message> complete = new ArrayList<Message>(); // synchronized private ArrayList<Message> commands = new ArrayList<Message>(); // synchronized private SampleView sampleView; public MessageQueue(SampleView sampleView, final ProcessMessage[] processes) { this.sampleView = sampleView; this.processes = processes; } public void start(final ByteOrder byteOrder, final FileInputStream file) { if (running) return; running = true; this.byteOrder = byteOrder; this.file = file; thread = new Thread(this); thread.start(); } public void stop() { if (!running) return; running = false; } public boolean isRunning() { return running; } private void sendCommands(final int contextId) throws IOException { synchronized (commands) { for (int i = 0; i < commands.size(); i++) { Message command = commands.get(i); if (command.getContextId() == contextId || command.getContextId() == 0) { sendMessage(commands.remove(i)); i--; } } } } public void addCommand(Message command) { synchronized (commands) { commands.add(command); } } // these should only be accessed from the network thread; // access call chain starts with run() private DataInputStream dis = null; private DataOutputStream dos = null; private SparseArray<ArrayList<Message>> incoming = new SparseArray<ArrayList<Message>>(); @Override public void run() { Socket socket = null; if (file == null) try { socket = new Socket(); socket.connect(new java.net.InetSocketAddress("127.0.0.1", Integer .parseInt(sampleView.actionPort.getText()))); dis = new DataInputStream(socket.getInputStream()); dos = new DataOutputStream(socket.getOutputStream()); } catch (Exception e) { running = false; Error(e); } else dis = new DataInputStream(file); while (running) { try { if (file != null && file.available() == 0) { running = false; break; } } catch (IOException e1) { e1.printStackTrace(); assert false; } Message msg = null; if (incoming.size() > 0) { // find queued incoming for (int i = 0; i < incoming.size(); i++) { final ArrayList<Message> messages = incoming.valueAt(i); if (messages.size() > 0) { msg = messages.remove(0); break; } } } try { if (null == msg) // get incoming from network msg = receiveMessage(dis); processMessage(dos, msg); } catch (IOException e) { Error(e); running = false; break; } } try { if (socket != null) socket.close(); else file.close(); } catch (IOException e) { Error(e); running = false; } } private void putMessage(final Message msg) { ArrayList<Message> existing = incoming.get(msg.getContextId()); if (existing == null) incoming.put(msg.getContextId(), existing = new ArrayList<Message>()); existing.add(msg); } Message receiveMessage(final int contextId) throws IOException { Message msg = receiveMessage(dis); while (msg.getContextId() != contextId) { putMessage(msg); msg = receiveMessage(dis); } return msg; } void sendMessage(final Message msg) throws IOException { sendMessage(dos, msg); } // should only be used by DefaultProcessMessage private SparseArray<Message> partials = new SparseArray<Message>(); Message getPartialMessage(final int contextId) { return partials.get(contextId); } // used to add BeforeCall to complete if it was skipped void completePartialMessage(final int contextId) { final Message msg = partials.get(contextId); partials.remove(contextId); assert msg != null; assert msg.getType() == Type.BeforeCall; if (msg != null) synchronized (complete) { complete.add(msg); } } // can be used by other message processor as default processor void defaultProcessMessage(final Message msg, boolean expectResponse, boolean sendResponse) throws IOException { final int contextId = msg.getContextId(); if (msg.getType() == Type.BeforeCall) { if (sendResponse) { final Message.Builder builder = Message.newBuilder(); builder.setContextId(contextId); builder.setType(Type.Response); builder.setExpectResponse(expectResponse); builder.setFunction(Function.CONTINUE); sendMessage(dos, builder.build()); } assert partials.indexOfKey(contextId) < 0; partials.put(contextId, msg); } else if (msg.getType() == Type.AfterCall) { if (sendResponse) { final Message.Builder builder = Message.newBuilder(); builder.setContextId(contextId); builder.setType(Type.Response); builder.setExpectResponse(expectResponse); builder.setFunction(Function.SKIP); sendMessage(dos, builder.build()); } assert partials.indexOfKey(contextId) >= 0; final Message before = partials.get(contextId); partials.remove(contextId); assert before.getFunction() == msg.getFunction(); final Message completed = before.toBuilder().mergeFrom(msg) .setType(Type.CompleteCall).build(); synchronized (complete) { complete.add(completed); } } else if (msg.getType() == Type.CompleteCall) { // this type should only be encountered on client after processing assert file != null; assert !msg.getExpectResponse(); assert !sendResponse; assert partials.indexOfKey(contextId) < 0; synchronized (complete) { complete.add(msg); } } else if (msg.getType() == Type.Response && msg.getFunction() == Function.SETPROP) { synchronized (complete) { complete.add(msg); } } else assert false; } public Message removeCompleteMessage(int contextId) { synchronized (complete) { if (complete.size() == 0) return null; if (0 == contextId) // get a message for any context return complete.remove(0); for (int i = 0; i < complete.size(); i++) { Message msg = complete.get(i); if (msg.getContextId() == contextId) { complete.remove(i); return msg; } } } return null; } private Message receiveMessage(final DataInputStream dis) throws IOException { int len = 0; try { len = dis.readInt(); if (byteOrder == ByteOrder.LITTLE_ENDIAN) len = Integer.reverseBytes(len); // readInt reads BIT_ENDIAN } catch (EOFException e) { Error(new Exception("EOF")); } byte[] buffer = new byte[len]; int readLen = 0; while (readLen < len) { int read = -1; try { read = dis.read(buffer, readLen, len - readLen); } catch (EOFException e) { Error(new Exception("EOF")); } if (read < 0) { Error(new Exception("read length = " + read)); return null; } else readLen += read; } Message msg = Message.parseFrom(buffer); sendCommands(msg.getContextId()); return msg; } private void sendMessage(final DataOutputStream dos, final Message message) throws IOException { if (dos == null) return; assert message.getFunction() != Function.NEG; final byte[] data = message.toByteArray(); if (byteOrder == ByteOrder.BIG_ENDIAN) dos.writeInt(data.length); else dos.writeInt(Integer.reverseBytes(data.length)); dos.write(data); } private void processMessage(final DataOutputStream dos, final Message msg) throws IOException { if (msg.getExpectResponse()) { assert dos != null; // readonly source cannot expectResponse for (ProcessMessage process : processes) if (process.processMessage(this, msg)) return; defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse()); } else defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse()); } void Error(Exception e) { sampleView.showError(e); } }