Home | History | Annotate | Download | only in gldebugger
      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