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 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