Forráskód Böngészése

- add iOS input handling base classes
- up version to 3.0.9

Normen Hansen 11 éve
szülő
commit
811e715c38

+ 1 - 1
engine/src/core/com/jme3/system/JmeVersion.java

@@ -32,5 +32,5 @@
 package com.jme3.system;
 
 public class JmeVersion {
-    public static final String FULL_NAME = "jMonkeyEngine 3.0.8";
+    public static final String FULL_NAME = "jMonkeyEngine 3.0.9";
 }

+ 203 - 0
engine/src/ios/com/jme3/input/ios/IosInputHandler.java

@@ -0,0 +1,203 @@
+package com.jme3.input.ios;
+
+import com.jme3.input.RawInputListener;
+import com.jme3.input.TouchInput;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.system.AppSettings;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class IosInputHandler implements TouchInput {
+    private static final Logger logger = Logger.getLogger(IosInputHandler.class.getName());
+
+    private final static int MAX_TOUCH_EVENTS = 1024;
+
+    // Custom settings
+    private boolean mouseEventsEnabled = true;
+    private boolean mouseEventsInvertX = false;
+    private boolean mouseEventsInvertY = false;
+    private boolean keyboardEventsEnabled = false;
+    private boolean dontSendHistory = false;
+
+    // Internal
+    private boolean initialized = false;
+    private RawInputListener listener = null;
+    private ConcurrentLinkedQueue<InputEvent> inputEventQueue = new ConcurrentLinkedQueue<InputEvent>();
+    private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
+    private IosTouchHandler touchHandler;
+    private float scaleX = 1f;
+    private float scaleY = 1f;
+    private int width = 0;
+    private int height = 0;
+
+    public IosInputHandler() {
+        touchHandler = new IosTouchHandler(this);
+    }
+    @Override
+    public void initialize() {
+        touchEventPool.initialize();
+        if (touchHandler != null) {
+            touchHandler.initialize();
+        }
+        initialized = true;
+    }
+
+    @Override
+    public void update() {
+         logger.log(Level.FINE, "InputEvent update : {0}", 
+                new Object[]{listener});
+       if (listener != null) {
+            InputEvent inputEvent;
+            
+            while ((inputEvent = inputEventQueue.poll()) != null) {
+                if (inputEvent instanceof TouchEvent) {
+                    listener.onTouchEvent((TouchEvent)inputEvent);
+                } else if (inputEvent instanceof MouseButtonEvent) {
+                    listener.onMouseButtonEvent((MouseButtonEvent)inputEvent);
+                } else if (inputEvent instanceof MouseMotionEvent) {
+                    listener.onMouseMotionEvent((MouseMotionEvent)inputEvent);
+                } else if (inputEvent instanceof KeyInputEvent) {
+                    listener.onKeyEvent((KeyInputEvent)inputEvent);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void destroy() {
+        initialized = false;
+        touchEventPool.destroy();
+        if (touchHandler != null) {
+            touchHandler.destroy();
+        }
+    }
+
+    @Override
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    @Override
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+
+    @Override
+    public void setSimulateMouse(boolean simulate) {
+        this.mouseEventsEnabled = simulate;
+    }
+
+    @Override
+    public boolean getSimulateMouse() {
+        return mouseEventsEnabled;
+    }
+
+    @Override
+    public boolean isSimulateMouse() {
+        return mouseEventsEnabled;
+    }
+
+    @Override
+    public void setSimulateKeyboard(boolean simulate) {
+        this.keyboardEventsEnabled = simulate;
+    }
+
+    @Override
+    public void setOmitHistoricEvents(boolean dontSendHistory) {
+        this.dontSendHistory = dontSendHistory;
+    }
+
+//    @Override
+    public void showVirtualKeyboard(boolean visible) {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    // ----------------
+        
+    public void loadSettings(AppSettings settings) {
+        // TODO: add simulate keyboard to settings
+//        keyboardEventsEnabled = true;
+        mouseEventsEnabled = true;//settings.isEmulateMouse();
+        mouseEventsInvertX = settings.isEmulateMouseFlipX();
+        mouseEventsInvertY = settings.isEmulateMouseFlipY();
+        
+        // view width and height are 0 until the view is displayed on the screen
+        //if (view.getWidth() != 0 && view.getHeight() != 0) {
+        //    scaleX = (float)settings.getWidth() / (float)view.getWidth();
+        //    scaleY = (float)settings.getHeight() / (float)view.getHeight();
+        //}
+        scaleX = 1.0f;
+        scaleY = 1.0f;
+        width = settings.getWidth();
+        height = settings.getHeight();
+        logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", 
+                new Object[]{scaleX, scaleY, width, height});
+    }
+    
+    public boolean isMouseEventsInvertX() {
+        return mouseEventsInvertX;
+    }
+
+    public boolean isMouseEventsInvertY() {
+        return mouseEventsInvertY;
+    }
+    
+    public float invertX(float origX) {
+        return getJmeX(width) - origX;
+    }
+    
+    public float invertY(float origY) {
+        return getJmeY(height) - origY;
+    }
+    
+    public float getJmeX(float origX) {
+        return origX * scaleX;
+    }
+    
+    public float getJmeY(float origY) {
+        return origY * scaleY;
+    }
+
+    public TouchEvent getFreeTouchEvent() {
+            return touchEventPool.getNextFreeEvent();
+    }
+    
+    public void addEvent(InputEvent event) {
+        inputEventQueue.add(event);
+        if (event instanceof TouchEvent) {
+            touchEventPool.storeEvent((TouchEvent)event);
+        }
+    }
+
+    // ----------------
+    
+    public void injectTouchDown(int pointerId, long time, float x, float y) {
+        logger.log(Level.FINE, "Using input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", 
+                new Object[]{scaleX, scaleY, width, height});
+        if (touchHandler != null) {
+            touchHandler.actionDown(pointerId, time, x, y);
+        }
+    }
+
+    public void injectTouchUp(int pointerId, long time, float x, float y) {
+        if (touchHandler != null) {
+            touchHandler.actionUp(pointerId, time, x, y);
+        }
+    }
+
+    public void injectTouchMove(int pointerId, long time, float x, float y) {
+        if (touchHandler != null) {
+            touchHandler.actionMove(pointerId, time, x, y);
+        }
+    }
+}

+ 191 - 0
engine/src/ios/com/jme3/input/ios/IosTouchHandler.java

@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.ios;
+
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import static com.jme3.input.event.TouchEvent.Type.DOWN;
+import static com.jme3.input.event.TouchEvent.Type.MOVE;
+import static com.jme3.input.event.TouchEvent.Type.UP;
+import com.jme3.math.Vector2f;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AndroidTouchHandler is the base class that receives touch inputs from the 
+ * Android system and creates the TouchEvents for jME.  This class is designed
+ * to handle the base touch events for Android rev 9 (Android 2.3).  This is
+ * extended by other classes to add features that were introducted after
+ * Android rev 9.
+ * 
+ * @author iwgeric
+ */
+public class IosTouchHandler {
+    private static final Logger logger = Logger.getLogger(IosTouchHandler.class.getName());
+    
+    final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
+
+    protected int numPointers = 1;
+    
+    protected IosInputHandler iosInput;
+
+    public IosTouchHandler(IosInputHandler iosInput) {
+        this.iosInput = iosInput;
+    }
+
+    public void initialize() {
+    }
+    
+    public void destroy() {
+    }
+    
+    public void actionDown(int pointerId, long time, float x, float y) {
+        logger.log(Level.FINE, "Inject input pointer: {0}, time: {1}, x: {2}, y: {3}", 
+                new Object[]{pointerId, time, x, y});
+        float jmeX = iosInput.getJmeX(x);
+        float jmeY = iosInput.invertY(iosInput.getJmeY(y));
+        TouchEvent touch = iosInput.getFreeTouchEvent();
+        touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0);
+        touch.setPointerId(pointerId);//TODO: pointer ID
+        touch.setTime(time);
+        touch.setPressure(1.0f);
+        //touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure
+
+        lastPositions.put(pointerId, new Vector2f(jmeX, jmeY));
+
+        processEvent(touch);
+    }
+    
+    public void actionUp(int pointerId, long time, float x, float y) {
+        float jmeX = iosInput.getJmeX(x);
+        float jmeY = iosInput.invertY(iosInput.getJmeY(y));
+        TouchEvent touch = iosInput.getFreeTouchEvent();
+        touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0);
+        touch.setPointerId(pointerId);//TODO: pointer ID
+        touch.setTime(time);
+        touch.setPressure(1.0f);
+        //touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure
+        lastPositions.remove(pointerId);
+
+        processEvent(touch);
+    }
+    
+    public void actionMove(int pointerId, long time, float x, float y) {
+        float jmeX = iosInput.getJmeX(x);
+        float jmeY = iosInput.invertY(iosInput.getJmeY(y));
+        Vector2f lastPos = lastPositions.get(pointerId);
+        if (lastPos == null) {
+            lastPos = new Vector2f(jmeX, jmeY);
+            lastPositions.put(pointerId, lastPos);
+        }
+
+        float dX = jmeX - lastPos.x;
+        float dY = jmeY - lastPos.y;
+        if (dX != 0 || dY != 0) {
+            TouchEvent touch = iosInput.getFreeTouchEvent();
+            touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY);
+            touch.setPointerId(pointerId);
+            touch.setTime(time);
+            touch.setPressure(1.0f);
+            //touch.setPressure(event.getPressure(p));
+            lastPos.set(jmeX, jmeY);
+
+            processEvent(touch);
+        }
+    }
+
+    protected void processEvent(TouchEvent event) {
+        // Add the touch event
+        iosInput.addEvent(event);
+        // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events
+        if (iosInput.isSimulateMouse() && numPointers == 1) {
+            InputEvent mouseEvent = generateMouseEvent(event);
+            if (mouseEvent != null) {
+                // Add the mouse event
+                iosInput.addEvent(mouseEvent);
+            }
+        }
+        
+    }
+
+    // TODO: Ring Buffer for mouse events?
+    protected InputEvent generateMouseEvent(TouchEvent event) {
+        InputEvent inputEvent = null;
+        int newX;
+        int newY;
+        int newDX;
+        int newDY;
+
+        if (iosInput.isMouseEventsInvertX()) {
+            newX = (int) (iosInput.invertX(event.getX()));
+            newDX = (int)event.getDeltaX() * -1;
+        } else {
+            newX = (int) event.getX();
+            newDX = (int)event.getDeltaX();
+        }
+
+        if (iosInput.isMouseEventsInvertY()) {
+            newY = (int) (iosInput.invertY(event.getY()));
+            newDY = (int)event.getDeltaY() * -1;
+        } else {
+            newY = (int) event.getY();
+            newDY = (int)event.getDeltaY();
+        }
+
+        switch (event.getType()) {
+            case DOWN:
+                // Handle mouse down event
+                inputEvent = new MouseButtonEvent(0, true, newX, newY);
+                inputEvent.setTime(event.getTime());
+                break;
+
+            case UP:
+                // Handle mouse up event
+                inputEvent = new MouseButtonEvent(0, false, newX, newY);
+                inputEvent.setTime(event.getTime());
+                break;
+
+//            case HOVER_MOVE:
+            case MOVE:
+                inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan());
+                inputEvent.setTime(event.getTime());
+                break;
+        }
+
+        return inputEvent;
+    }
+    
+}

+ 121 - 0
engine/src/ios/com/jme3/input/ios/TouchEventPool.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input.ios;
+
+import com.jme3.input.event.TouchEvent;
+import com.jme3.util.RingBuffer;
+import java.util.logging.Logger;
+
+/**
+ * TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage
+ * collection on Android.  Each TouchEvent is stored in the RingBuffer and is 
+ * reused if the TouchEvent has been consumed.
+ * 
+ * If a TouchEvent has not been consumed, it is placed back into the pool at the 
+ * end for later use.  If a TouchEvent has been consumed, it is reused to avoid
+ * creating lots of little objects.
+ * 
+ * If the pool is full of unconsumed events, then a new event is created and provided.
+ * 
+ * 
+ * @author iwgeric
+ */
+public class TouchEventPool {
+    private static final Logger logger = Logger.getLogger(TouchEventPool.class.getName());
+    private final RingBuffer<TouchEvent> eventPool;
+    private final int maxEvents;
+    
+    public TouchEventPool (int maxEvents) {
+        eventPool = new RingBuffer<TouchEvent>(maxEvents);
+        this.maxEvents = maxEvents;
+    } 
+
+    public void initialize() {
+        TouchEvent newEvent;
+        while (!eventPool.isEmpty()) {
+            eventPool.pop();
+        }
+        for (int i = 0; i < maxEvents; i++) {
+            newEvent = new TouchEvent();
+            newEvent.setConsumed();
+            eventPool.push(newEvent);
+        }
+    }
+    
+    public void destroy() {
+        // Clean up queues
+        while (!eventPool.isEmpty()) {
+            eventPool.pop();
+        }
+    }
+
+    /**
+     * Fetches a touch event from the reuse pool
+     *
+     * @return a usable TouchEvent
+     */
+    public TouchEvent getNextFreeEvent() {
+        TouchEvent evt = null;
+        int curSize = eventPool.size();
+        while (curSize > 0) {
+            evt = (TouchEvent)eventPool.pop();
+            if (evt.isConsumed()) {
+                break;
+            } else {
+                eventPool.push(evt);
+                evt = null;
+            }
+            curSize--;
+        }
+
+        if (evt == null) {
+            logger.warning("eventPool full of unconsumed events");
+            evt = new TouchEvent();
+        }
+        return evt;
+    }
+    
+    /**
+     * Stores the TouchEvent back in the pool for later reuse.  It is only reused
+     * if the TouchEvent has been consumed.
+     * 
+     * @param event TouchEvent to store for later use if consumed.
+     */
+    public void storeEvent(TouchEvent event) {
+        if (eventPool.size() < maxEvents) {
+            eventPool.push(event);
+        } else {
+            logger.warning("eventPool full");
+        }
+    }    
+    
+}

+ 8 - 9
engine/src/ios/com/jme3/system/ios/IGLESContext.java

@@ -37,6 +37,7 @@ import com.jme3.input.dummy.DummyKeyInput;
 import com.jme3.input.dummy.DummyMouseInput;
 import com.jme3.renderer.ios.IGLESShaderRenderer;
 import com.jme3.system.*;
+import com.jme3.input.ios.IosInputHandler;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -56,6 +57,7 @@ public class IGLESContext implements JmeContext {
     protected IGLESShaderRenderer renderer;
     protected Timer timer;
     protected SystemListener listener;
+    protected IosInputHandler input;
     protected int minFrameDuration = 0;                   // No FPS cap
 
     public IGLESContext() {
@@ -63,20 +65,17 @@ public class IGLESContext implements JmeContext {
     }
 
     @Override
-    public Type getType() {
-        return Type.Display;
+    public JmeContext.Type getType() {
+        return JmeContext.Type.Display;
     }
 
     @Override
     public void setSettings(AppSettings settings) {
         logger.log(Level.FINE, "IGLESContext setSettings");
         this.settings.copyFrom(settings);
-        /*
-        if (androidInput != null) {
-            androidInput.loadSettings(settings);
+        if (input != null) {
+            input.loadSettings(settings);
         }
-        */
-
     }
 
     @Override
@@ -119,8 +118,7 @@ public class IGLESContext implements JmeContext {
 
     @Override
     public TouchInput getTouchInput() {
-        //return androidInput;
-        return null;//  new DummyTouchInput();
+        return input;
     }
 
     @Override
@@ -153,6 +151,7 @@ public class IGLESContext implements JmeContext {
     public void create(boolean waitFor) {
         logger.log(Level.FINE, "IGLESContext create");
         renderer = new IGLESShaderRenderer();
+        input = new IosInputHandler();
         timer = new NanoTimer();
 
 //synchronized (createdLock){

+ 76 - 0
engine/src/ios/com/jme3/util/RingBuffer.java

@@ -0,0 +1,76 @@
+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<T> implements Iterable<T> {
+
+    private T[] 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 = (T[]) new Object[capacity];
+    }
+
+    public boolean isEmpty() {
+        return count == 0;
+    }
+
+    public int size() {
+        return count;
+    }
+
+    public void push(T item) {
+        if (count == buffer.length) {
+            throw new RuntimeException("Ring buffer overflow");
+        }
+        buffer[indexIn] = item;
+        indexIn = (indexIn + 1) % buffer.length;     // wrap-around
+        count++;
+    }
+
+    public T pop() {
+        if (isEmpty()) {
+            throw new RuntimeException("Ring buffer underflow");
+        }
+        T item = buffer[indexOut];
+        buffer[indexOut] = null;                  // to help with garbage collection
+        count--;
+        indexOut = (indexOut + 1) % buffer.length; // wrap-around
+        return item;
+    }
+
+    public Iterator<T> iterator() {
+        return new RingBufferIterator();
+    }
+
+    // an iterator, doesn't implement remove() since it's optional
+    private class RingBufferIterator implements Iterator<T> {
+
+        private int i = 0;
+
+        public boolean hasNext() {
+            return i < count;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        public T next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            return buffer[i++];
+        }
+    }
+}

+ 1 - 1
sdk/nbproject/project.properties

@@ -6,7 +6,7 @@ app.icon.icns=jmonkeyplatform.icns
 #version name used for application and settings folder, no spaces!
 app.version=3.0
 #version number used for plugins, only 3 numbers (e.g. 3.1.3)
-plugins.version=3.0.8
+plugins.version=3.0.9
 #used netbeans platform
 netbeans.platform.url=http://download.netbeans.org/netbeans/7.3.1/final/zip/netbeans-7.3.1-201306052037-javase.zip
 #command line args