diff --git a/engine/src/android/com/jme3/input/android/AndroidInput.java b/engine/src/android/com/jme3/input/android/AndroidInput.java index 0df8313f2..a6d56f7a6 100644 --- a/engine/src/android/com/jme3/input/android/AndroidInput.java +++ b/engine/src/android/com/jme3/input/android/AndroidInput.java @@ -1,9 +1,7 @@ package com.jme3.input.android; -import java.util.List; -import java.util.ArrayList; +import java.util.HashMap; import java.util.logging.Logger; - import android.content.Context; import android.opengl.GLSurfaceView; import android.util.AttributeSet; @@ -11,35 +9,37 @@ import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ScaleGestureDetector; -import com.jme3.input.android.TouchEvent; import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; import com.jme3.input.RawInputListener; -import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.TouchInput; import com.jme3.input.event.MouseButtonEvent; import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.input.event.TouchEvent.Type; import com.jme3.math.Vector2f; +import com.jme3.util.RingBuffer; -public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, +public class AndroidInput extends GLSurfaceView implements TouchInput, GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener { private final static Logger logger = Logger.getLogger(AndroidInput.class.getName()); + private boolean isInitialized = false; + private RawInputListener listener = null; + + final private static int MAX_EVENTS = 1024; - private RawInputListener listenerRaw = null; - private AndroidTouchInputListener listenerTouch = null; + final private RingBuffer eventQueue = new RingBuffer(MAX_EVENTS); + final private RingBuffer eventPool = new RingBuffer(MAX_EVENTS); + final private HashMap lastPositions = new HashMap(); + + public boolean fireMouseEvents = true; + public boolean fireKeyboardEvents = false; + private ScaleGestureDetector scaledetector; private GestureDetector detector; private int lastX; private int lastY; - private boolean dragging = false; - - private List currentEvents = new ArrayList(); - - private final static int MAX_EVENTS = 1024; - - - private boolean FIRE_MOUSE_EVENTS = true; private static final int[] ANDROID_TO_JME = { @@ -158,7 +158,41 @@ public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, detector=new GestureDetector(this); scaledetector=new ScaleGestureDetector(ctx, this); } + + private TouchEvent getNextFreeTouchEvent() + { + return getNextFreeTouchEvent(false); + } + private TouchEvent getNextFreeTouchEvent(boolean wait) + { + TouchEvent evt; + if (eventPool.isEmpty() && wait) + { + logger.warning("eventPool buffer underrun"); + boolean isEmpty; + do + { + synchronized(eventPool) + { + isEmpty = eventPool.isEmpty(); + } + try { Thread.sleep(50); } catch (InterruptedException e) { } + } + while (isEmpty); + evt = eventPool.pop(); + } + else if (eventPool.isEmpty()) + { + evt = new TouchEvent(); + logger.warning("eventPool buffer underrun"); + } + else + { + evt = eventPool.pop(); + } + return evt; + } /** * onTouchEvent gets called from android thread on touchpad events */ @@ -166,94 +200,62 @@ public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, public boolean onTouchEvent(MotionEvent event) { boolean bWasHandled = false; - MouseButtonEvent btn; TouchEvent touch; - - // Send the raw event - processEvent(event); // Try to detect gestures this.detector.onTouchEvent(event); this.scaledetector.onTouchEvent(event); - int newX = getWidth() - (int) event.getX(); - int newY = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - - if (FIRE_MOUSE_EVENTS) - { - // Handle mouse events - btn = new MouseButtonEvent(0, true, newX, newY); - btn.setTime(event.getEventTime()); - processEvent(btn); + + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) + { + touch = getNextFreeTouchEvent(); + touch.set(Type.DOWN, event.getX(p), event.getY(p), 0, 0); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); + processEvent(touch); } - // Store current pos - lastX = -1; - lastY = -1; - - // Handle gesture events - touch = new TouchEvent(TouchEvent.Type.GRABBED, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); - processEvent(touch); bWasHandled = true; break; case MotionEvent.ACTION_UP: - if (FIRE_MOUSE_EVENTS) - { - // Handle mouse events - btn = new MouseButtonEvent(0, false, newX, newY); - btn.setTime(event.getEventTime()); - processEvent(btn); - } - // Store current pos - lastX = -1; - lastY = -1; - - // Handle gesture events - if(dragging) - { - touch = new TouchEvent(TouchEvent.Type.DRAGGED, TouchEvent.Operation.STOPPED,event.getX(),event.getY(),event.getX()-lastX,event.getY()-lastY,null); + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) + { + touch = getNextFreeTouchEvent(); + touch.set(Type.UP, event.getX(p), event.getY(p), 0, 0); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); processEvent(touch); } - touch = new TouchEvent(TouchEvent.Type.RELEASED, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); - processEvent(touch); - dragging=false; + bWasHandled = true; break; case MotionEvent.ACTION_MOVE: - if(!scaledetector.isInProgress()) - { - if(!dragging) - touch = new TouchEvent(TouchEvent.Type.DRAGGED, TouchEvent.Operation.STARTED,event.getX(),event.getY(),event.getX()-lastX,event.getY()-lastY,null); - else - touch = new TouchEvent(TouchEvent.Type.DRAGGED, TouchEvent.Operation.RUNNING,event.getX(),event.getY(),event.getX()-lastX,event.getY()-lastY,null); - + + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) + { + Vector2f lastPos = lastPositions.get(event.getPointerId(p)); + if (lastPos == null) + { + lastPos = new Vector2f(event.getX(p), event.getY(p)); + lastPositions.put(event.getPointerId(p), lastPos); + } + touch = getNextFreeTouchEvent(); + touch.set(Type.MOVE, event.getX(p), event.getY(p), event.getX(p) - lastPos.x, event.getY(p) - lastPos.y); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); processEvent(touch); - dragging=true; + lastPos.set(event.getX(p), event.getY(p)); } - if (FIRE_MOUSE_EVENTS) - { - - int dx; - int dy; - if (lastX != -1){ - dx = newX - lastX; - dy = newY - lastY; - }else{ - dx = 0; - dy = 0; - } - MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0); - mot.setTime(event.getEventTime()); - processEvent(mot); - } - lastX = newX; - lastY = newY; bWasHandled = true; break; @@ -274,20 +276,18 @@ public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, } @Override - public boolean onKeyDown (int keyCode, KeyEvent event) { - - // Send the raw event - processEvent(event); + public boolean onKeyDown (int keyCode, KeyEvent event) + { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); - int jmeCode = ANDROID_TO_JME[keyCode]; - if (jmeCode != 0) - { - String str = event.getCharacters(); - char c = str != null && str.length() > 0 ? str.charAt(0) : 0x0; - KeyInputEvent evt = new KeyInputEvent(jmeCode, c, true, false); - logger.info("onKeyDown " + evt); - processEvent(evt); - } // Handle all keys ourself, except the back button (4) if (keyCode == 4) return false; @@ -296,20 +296,17 @@ public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, } @Override - public boolean onKeyUp (int keyCode, KeyEvent event) { + public boolean onKeyUp (int keyCode, KeyEvent event) + { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); - // Send the raw event - processEvent(event); - - int jmeCode = ANDROID_TO_JME[keyCode]; - if (jmeCode != 0) - { - String str = event.getCharacters(); - char c = str != null && str.length() > 0 ? str.charAt(0) : 0x0; - KeyInputEvent evt = new KeyInputEvent(jmeCode, c, false, false); - logger.info("onKeyUp " + evt); - processEvent(evt); - } + // Send the event + processEvent(evt); // Handle all keys ourself, except the back button (4) if (keyCode == 4) @@ -318,80 +315,142 @@ public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, return true; } - public void setCursorVisible(boolean visible){ - } - public int getButtonCount(){ - return 255; + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() + { + TouchEvent item; + for (int i = 0; i < MAX_EVENTS; i++) + { + item = new TouchEvent(); + eventPool.push(item); + } + isInitialized = true; } - - public void initialize() { + + @Override + public void destroy() + { + isInitialized = false; + + // Clean up queues + while (! eventPool.isEmpty()) + { + eventPool.pop(); + } + while (! eventQueue.isEmpty()) + { + eventQueue.pop(); + } } - - public void update() { - generateEvents(); + + @Override + public boolean isInitialized() + { + return isInitialized; } - - public void destroy() { + + @Override + public void setInputListener(RawInputListener listener) + { + this.listener = listener; } - - public boolean isInitialized() { - return true; + + @Override + public long getInputTimeNanos() + { + return System.nanoTime(); } + // ----------------------------------------- - - - private void processEvent(Object event) + private void processEvent(TouchEvent event) { - synchronized (currentEvents) { - if (currentEvents.size() < MAX_EVENTS) - currentEvents.add(event); + synchronized (eventQueue) + { + eventQueue.push(event); } } - Object event; - private void generateEvents() { - if (listenerRaw != null) - { - synchronized (currentEvents) { - //for (Object event: currentEvents) { - for (int i = 0; i < currentEvents.size(); i++) { - event = currentEvents.get(i); - if (event instanceof MouseButtonEvent) { - listenerRaw.onMouseButtonEvent((MouseButtonEvent) event); - } else if (event instanceof MouseMotionEvent) { - listenerRaw.onMouseMotionEvent((MouseMotionEvent) event); - } else if (event instanceof KeyInputEvent) { - listenerRaw.onKeyEvent((KeyInputEvent) event); - } else if (event instanceof TouchEvent) { - if (listenerTouch != null) - listenerTouch.onTouchEvent((TouchEvent) event); - } else if (event instanceof MotionEvent) { - if (listenerTouch != null) - listenerTouch.onMotionEvent((MotionEvent) event); - } else if (event instanceof KeyEvent) { - if (listenerTouch != null) - listenerTouch.onAndroidKeyEvent((KeyEvent) event); - } - } - currentEvents.clear(); - } - } - } - - public void setInputListener(RawInputListener listener) { - this.listenerRaw = listener; - } - - public void setInputListener(AndroidTouchInputListener listener) { - this.listenerRaw = listener; - this.listenerTouch = listener; - } - - public long getInputTimeNanos() { - return System.nanoTime(); + @Override + public void update() + { + generateEvents(); } + + private void generateEvents() + { + if (listener != null) + { + TouchEvent event; + MouseButtonEvent btn; + int newX; + int newY; + + while (!eventQueue.isEmpty()) + { + synchronized (eventQueue) + { + event = eventQueue.pop(); + } + if (event != null) + { + listener.onTouchEvent(event); + + if (fireMouseEvents) + { + newX = getWidth() - (int) event.getX(); + newY = (int) event.getY(); + switch (event.getType()) + { + case DOWN: + // Handle mouse events + btn = new MouseButtonEvent(0, true, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case UP: + // Handle mouse events + btn = new MouseButtonEvent(0, false, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case MOVE: + int dx; + int dy; + if (lastX != -1){ + dx = newX - lastX; + dy = newY - lastY; + }else{ + dx = 0; + dy = 0; + } + MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0); + mot.setTime(event.getTime()); + listener.onMouseMotionEvent(mot); + lastX = newX; + lastY = newY; + break; + } + } + } + synchronized (eventPool) + { + eventPool.push(event); + } + } + } + } // --------------- Gesture detected callback events ---------------------------------- @@ -401,75 +460,122 @@ public class AndroidInput extends GLSurfaceView implements KeyInput, MouseInput, } public void onLongPress(MotionEvent event) - { - TouchEvent touch = new TouchEvent(TouchEvent.Type.LONGPRESSED, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.LONGPRESSED, event.getX(), event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); processEvent(touch); } public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) { - TouchEvent touch = new TouchEvent(TouchEvent.Type.FLING, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.FLING, event.getX(), event.getY(), vx, vy); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); processEvent(touch); + return true; } public boolean onSingleTapConfirmed(MotionEvent event) - { - TouchEvent touch = new TouchEvent(TouchEvent.Type.TAP, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.TAP, event.getX(), event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); processEvent(touch); + return true; } public boolean onDoubleTap(MotionEvent event) { - TouchEvent touch = new TouchEvent(TouchEvent.Type.DOUBLETAP, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); - processEvent(touch); + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.DOUBLETAP, event.getX(), event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); return true; } public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) - { - TouchEvent touch = new TouchEvent(TouchEvent.Type.SCALE, TouchEvent.Operation.STARTED,scaleGestureDetector.getFocusX(),scaleGestureDetector.getFocusY(),0,0,new float[]{scaleGestureDetector.getCurrentSpan(),scaleGestureDetector.getScaleFactor()}); - processEvent(touch); + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + return true; } public boolean onScale(ScaleGestureDetector scaleGestureDetector) { - TouchEvent touch = new TouchEvent(TouchEvent.Type.SCALE, TouchEvent.Operation.RUNNING,scaleGestureDetector.getFocusX(),scaleGestureDetector.getFocusY(),0,0,new float[]{scaleGestureDetector.getCurrentSpan(),scaleGestureDetector.getScaleFactor()}); - processEvent(touch); - - if (FIRE_MOUSE_EVENTS) - { - MouseMotionEvent mot = new MouseMotionEvent(0, 0, 0, 0, 0, (int)scaleGestureDetector.getScaleFactor()); - mot.setTime(scaleGestureDetector.getEventTime()); - processEvent(mot); - } + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + return false; } public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) - { - TouchEvent touch = new TouchEvent(TouchEvent.Type.SCALE, TouchEvent.Operation.STOPPED,scaleGestureDetector.getFocusX(),scaleGestureDetector.getFocusY(),0,0,new float[]{scaleGestureDetector.getCurrentSpan(),scaleGestureDetector.getScaleFactor()}); - processEvent(touch); + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); } - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, - float distanceY) { - // TODO Auto-generated method stub + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCROLL, e1.getX(), e1.getY(), distanceX, distanceY); + touch.setPointerId(0); + touch.setTime(e1.getEventTime()); + processEvent(touch); return false; } - public void onShowPress(MotionEvent e) { - // TODO Auto-generated method stub - + public void onShowPress(MotionEvent event) + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SHOWPRESS, event.getX(), event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); } public boolean onSingleTapUp(MotionEvent event) - { - TouchEvent touch = new TouchEvent(TouchEvent.Type.TAP, TouchEvent.Operation.NOP,event.getX(),event.getY(),0,0,null); + { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.TAP, event.getX(), event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); processEvent(touch); return true; } + @Override + public void setSimulateMouse(boolean simulate) + { + fireMouseEvents = simulate; + } + + @Override + public void setSimulateKeyboard(boolean simulate) + { + fireKeyboardEvents = simulate; + } + } diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java index 003a028e9..11dbf0aff 100644 --- a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java +++ b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java @@ -1,6 +1,7 @@ package com.jme3.input.android; import com.jme3.input.RawInputListener; +import com.jme3.input.event.TouchEvent; import android.view.KeyEvent; import android.view.MotionEvent; diff --git a/engine/src/android/com/jme3/input/android/TouchEvent.java b/engine/src/android/com/jme3/input/android/TouchEvent.java deleted file mode 100644 index e7c31fe5c..000000000 --- a/engine/src/android/com/jme3/input/android/TouchEvent.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.jme3.input.android; - -import com.jme3.math.Vector2f; - -public class TouchEvent -{ - public static enum Type {GRABBED,DRAGGED,RELEASED,FLING,TAP,DOUBLETAP,LONGPRESSED,SCALE,OUTSIDE,IDLE} - public Type type=Type.IDLE; - - public static enum Operation {NOP,STARTED,RUNNING,STOPPED,CANCELED} - private Operation operation=Operation.NOP; - - public float x; - public float y; - public float deltax; - public float deltay; - public float[] extra; - - public TouchEvent(Type type, Operation operation, float x, float y, float deltax, float deltay, float[] extra) - { - set(type, operation, x, y, deltax, deltay, extra); - } - - public void set( Type type, Operation operation, float x, float y, float deltax, float deltay, float[] extra) - { - this.type=type; - this.operation=operation; - this.x=x; - this.y=y; - this.deltax=deltax; - this.deltay=deltay; - this.extra=extra; - } - - - public Type getType() - { - return type; - } - - public Operation getOperation() - { - return operation; - } - - - public float getX() - { - return x; - } - - public float getY() - { - return y; - } - - public float getDeltaX() - { - return deltax; - } - - public float getDeltaY() - { - return deltay; - } - - public float[] getExtra() - { - return extra; - } - - public Vector2f getDelta() - { - return new Vector2f(deltax,deltay); - } -} diff --git a/engine/src/android/com/jme3/system/android/OGLESContext.java b/engine/src/android/com/jme3/system/android/OGLESContext.java index 8c218a01c..e3b3200f4 100644 --- a/engine/src/android/com/jme3/system/android/OGLESContext.java +++ b/engine/src/android/com/jme3/system/android/OGLESContext.java @@ -41,7 +41,10 @@ import com.jme3.app.AndroidHarness; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; import com.jme3.input.android.AndroidInput; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; import com.jme3.renderer.android.OGLESShaderRenderer; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; @@ -231,18 +234,23 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer @Override public MouseInput getMouseInput() { - return view; + return new DummyMouseInput(); } @Override public KeyInput getKeyInput() { - return view; + return new DummyKeyInput(); } @Override public JoyInput getJoyInput() { return null; } + + @Override + public TouchInput getTouchInput() { + return view; + } @Override public Timer getTimer() @@ -386,4 +394,5 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer } } + } diff --git a/engine/src/android/com/jme3/util/RingBuffer.java b/engine/src/android/com/jme3/util/RingBuffer.java new file mode 100644 index 000000000..cd6393cc8 --- /dev/null +++ b/engine/src/android/com/jme3/util/RingBuffer.java @@ -0,0 +1,63 @@ +package com.jme3.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Ring buffer (fixed size queue) implementation using a circular array (array with wrap-around). + */ +// suppress unchecked warnings in Java 1.5.0_6 and later +@SuppressWarnings("unchecked") +public class RingBuffer implements Iterable +{ + private Item[] buffer; // queue elements + private int count = 0; // number of elements on queue + private int indexOut = 0; // index of first element of queue + private int indexIn = 0; // index of next available slot + + // cast needed since no generic array creation in Java + public RingBuffer(int capacity) + { + buffer = (Item[]) new Object[capacity]; + } + + public boolean isEmpty() { return count == 0; } + public int size() { return count; } + + public void push(Item item) + { + if (count == buffer.length) { throw new RuntimeException("Ring buffer overflow"); } + buffer[indexIn] = item; + indexIn = (indexIn + 1) % buffer.length; // wrap-around + count++; + } + + public Item pop() + { + if (isEmpty()) { throw new RuntimeException("Ring buffer underflow"); } + Item item = buffer[indexOut]; + buffer[indexOut] = null; // to help with garbage collection + count--; + indexOut = (indexOut + 1) % buffer.length; // wrap-around + return item; + } + + public Iterator iterator() { return new RingBufferIterator(); } + + // an iterator, doesn't implement remove() since it's optional + private class RingBufferIterator implements Iterator { + private int i = 0; + public boolean hasNext() { return i < count; } + public void remove() { throw new UnsupportedOperationException(); } + + public Item next() { + if (!hasNext()) throw new NoSuchElementException(); + return buffer[i++]; + } + } + + + + +} +