Merge: app.nit: navigate between windows with the back button
authorJean Privat <jean@pryen.org>
Tue, 17 May 2016 20:17:43 +0000 (16:17 -0400)
committerJean Privat <jean@pryen.org>
Tue, 17 May 2016 20:17:43 +0000 (16:17 -0400)
This should be the last big feature of _app.nit_ needed for my thesis!

Intro services to navigate between multiple windows of the same app.
Windows are added to a stack and popped back to the visible state on pressing the back key.
This behavior was implemented for Android by intercepting events raised by the "hardware" back key.
iOS offers the same features natively so there is no adaptation needed as of now.
On GNU/Linux with GTK+, a button is added to the window header when there's a window to go back to.

These feature will probably have to be tweaked in the future, but they are enough for the Benilux client app. Notably, the life-cylce of each individual window has to be reconsidered and we will probably need some adaptation on iOS to have a better integration.

This PR also fixes a few bugs. The `EditText` losing focus in a `ListLayout` is fixed by a change to the Android manifest file, and vertical layout should now look better on iOS.

I took the opportunity to add other key events while working on the Android back key support.
These will be useful when migrating games from the old low-level implementation on the NDK to our custom `NitActivity.java`.

Pull-Request: #2100
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>

37 files changed:
contrib/asteronits/src/android.nit
contrib/asteronits/src/touch_ui.nit
contrib/tnitter/src/tnitter_app_android.nit
examples/calculator/src/android_calculator.nit
lib/android/README.md
lib/android/android.nit
lib/android/game.nit [new file with mode: 0644]
lib/android/input_events.nit
lib/android/landscape.nit
lib/android/portrait.nit
lib/android/sensors.nit
lib/android/ui/ui.nit
lib/app/audio.nit
lib/app/data_store.nit
lib/app/ui.nit
lib/core/text/flat.nit
lib/gamnit/display_android.nit
lib/ios/ui/ui.nit
lib/linux/ui.nit
lib/mnit/android/android_app.nit
lib/postgresql/native_postgres.nit [new file with mode: 0644]
lib/postgresql/package.ini [new file with mode: 0644]
lib/rubix.ini
share/man/nitunit.md
src/model/model_collect.nit
src/model/model_json.nit [new file with mode: 0644]
src/modelize/modelize_property.nit
src/nitlight.nit
src/nitunit.nit
tests/base_redef.nit [new file with mode: 0644]
tests/sav/base_redef.res [new file with mode: 0644]
tests/sav/base_redef_alt1.res [new file with mode: 0644]
tests/sav/base_redef_alt2.res [new file with mode: 0644]
tests/sav/test_postgres_native.res [new file with mode: 0644]
tests/sav/test_substring.res
tests/test_postgres_native.nit [new file with mode: 0644]
tests/test_substring.nit

index 4042f6d..e7ac9df 100644 (file)
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import ::android::platform
+import ::android
 import ::android::vibration
 
 import asteronits
index 3971426..388cd35 100644 (file)
@@ -62,16 +62,16 @@ redef class App
                                        if dy > 0.0 then
                                                # Bottom part of the joystick, turns left or right
                                                if dx < 0.0 then
-                                                       ship.applied_rotation = -1.0
-                                               else
                                                        ship.applied_rotation = 1.0
+                                               else
+                                                       ship.applied_rotation = -1.0
                                                end
                                        else
                                                # Upper part of the joystick, detect action using 45d angles
                                                if dx < dy then
-                                                       ship.applied_rotation = -1.0
-                                               else if dx > -dy then
                                                        ship.applied_rotation = 1.0
+                                               else if dx > -dy then
+                                                       ship.applied_rotation = -1.0
                                                else
                                                        ship.applied_thrust = 1.0
                                                end
@@ -98,20 +98,20 @@ redef class App
 
                # Add the joystick to the UI
                ui_sprites.add new Sprite(spritesheet_controls.forward,
-                       ui_camera.bottom_left.offset(joystick_x, -200.0, 0.0))
+                       ui_camera.bottom_left.offset(joystick_x, 200.0, 0.0))
                ui_sprites.add new Sprite(spritesheet_controls.left,
-                       ui_camera.bottom_left.offset(joystick_x-100.0, -joystick_y, 0.0))
+                       ui_camera.bottom_left.offset(joystick_x-100.0, joystick_y, 0.0))
                ui_sprites.add new Sprite(spritesheet_controls.right,
-                       ui_camera.bottom_left.offset(joystick_x+100.0, -joystick_y, 0.0))
+                       ui_camera.bottom_left.offset(joystick_x+100.0, joystick_y, 0.0))
 
                # Purely cosmetic joystick background
                ui_sprites.add new Sprite(spritesheet_controls.joystick_back,
-                       ui_camera.bottom_left.offset(joystick_x, -joystick_y, -1.0)) # In the back
+                       ui_camera.bottom_left.offset(joystick_x, joystick_y, -1.0)) # In the back
                ui_sprites.add new Sprite(spritesheet_controls.joystick_down,
                        ui_camera.bottom_left.offset(joystick_x, 0.0, 1.0))
 
                # Add the "open fire" button
                ui_sprites.add new Sprite(spritesheet_controls.fire,
-                       ui_camera.bottom_right.offset(-150.0, -150.0, 0.0))
+                       ui_camera.bottom_right.offset(-150.0, 150.0, 0.0))
        end
 end
index 74eb1b6..dc24681 100644 (file)
@@ -19,8 +19,7 @@ end
 
 import tnitter_app
 
-import android::ui
-import android::http_request
+import android
 import android::portrait
 
 redef class LabelAuthor
index 7fe192c..1d51073 100644 (file)
@@ -16,7 +16,7 @@
 module android_calculator
 
 import calculator
-import android::ui
+import android
 
 redef class Button
        init do set_android_style(native, (text or else "?").is_int)
index 47e0cec..b6bcf7e 100644 (file)
@@ -64,7 +64,16 @@ integer as argument. They are applied in the Android manifest as
   only be used by low-level implementations of Nit on Android.
   Its usefulness will be extended in the future to customize user applications.
 
-## Project entry points
+## Android implementation
+
+There is two core implementation for Nit apps on Android.
+`android::nit_activity` is used by apps with standard windows and native UI controls.
+`android::game` is used by, well, games and the game frameworks `mnit` and `gamnit`.
+
+Clients don't have to select the core implementation, it is imported by other relevant modules.
+For example, a module importing `app::ui` and `android` will trigger the importation of `android::nit_activity`.
+
+## Lock app orientation
 
 Importing `android::landscape` or `android::portrait` locks the generated
 application in the specified orientation. This can be useful for games and
index b5aa04f..58e296a 100644 (file)
 module android
 
 import platform
-import native_app_glue
 import dalvik
 private import log
-private import assets
-
-redef class App
-       redef fun init_window
-       do
-               super
-               on_create
-               on_restore_state
-               on_start
-       end
-
-       redef fun term_window
-       do
-               super
-               on_stop
-       end
-
-       # Is the application currently paused?
-       var paused = true
-
-       redef fun pause
-       do
-               paused = true
-               on_pause
-               super
-       end
-
-       redef fun resume
-       do
-               paused = false
-               on_resume
-               super
-       end
-
-       redef fun save_state do on_save_state
-
-       redef fun lost_focus
-       do
-               paused = true
-               super
-       end
-
-       redef fun gained_focus
-       do
-               paused = false
-               super
-       end
-
-       redef fun destroy do on_destroy
-end
diff --git a/lib/android/game.nit b/lib/android/game.nit
new file mode 100644 (file)
index 0000000..2d17054
--- /dev/null
@@ -0,0 +1,71 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Android services and implementation of app.nit for gamnit and mnit
+module game
+
+import platform
+import native_app_glue
+import dalvik
+private import log
+private import assets
+
+redef class App
+       redef fun init_window
+       do
+               super
+               on_create
+               on_restore_state
+               on_start
+       end
+
+       redef fun term_window
+       do
+               super
+               on_stop
+       end
+
+       # Is the application currently paused?
+       var paused = true
+
+       redef fun pause
+       do
+               paused = true
+               on_pause
+               super
+       end
+
+       redef fun resume
+       do
+               paused = false
+               on_resume
+               super
+       end
+
+       redef fun save_state do on_save_state
+
+       redef fun lost_focus
+       do
+               paused = true
+               super
+       end
+
+       redef fun gained_focus
+       do
+               paused = false
+               super
+       end
+
+       redef fun destroy do on_destroy
+end
index 9597eea..8aea378 100644 (file)
@@ -18,7 +18,7 @@
 module input_events
 
 import mnit::input
-import android
+import android::game
 
 in "C header" `{
        #include <android/log.h>
index be79eb5..c313efb 100644 (file)
@@ -20,4 +20,4 @@ module landscape is
        android_manifest_activity """android:screenOrientation="sensorLandscape" """
 end
 
-import platform
+import android
index e8f5720..0759c40 100644 (file)
@@ -17,4 +17,4 @@ module portrait is android_manifest_activity """
                android:screenOrientation="portrait"
 """
 
-import platform
+import android
index b969389..0681e4a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This module is used to manipulate android sensors
-# The sensor support is implemented in android_app module, so the user can enable the type of sensor he wants to use.
-# There is an example of how you can use the android sensors in nit/examples/mnit_ballz :
+# Access Android sensors
+#
+# Sensors are to be enabled when `App` is created.
+# The following example enables all sensors.
+# The events (`SensorEvent`, `ASensorAccelerometer`, `ASensorMagneticField`...)
+# are sent to the `input` callback of `App`
 #
 # ~~~~nitish
-# #FIXME rewrite the example
 # redef class App
-#      sensors_support_enabled = true
-#      accelerometer.enabled = true
-#      accelerometer.eventrate = 10000
-#      magnetic_field.enabled = true
-#      gyroscope.enabled = true
-#      light.enabled = true
-#      proximity.enabled = true
+#     init
+#     do
+#         sensors_support_enabled = true
+#         accelerometer.enabled = true
+#         accelerometer.eventrate = 10000
+#         magnetic_field.enabled = true
+#         gyroscope.enabled = true
+#         light.enabled = true
+#         proximity.enabled = true
+#     end
 # end
 # ~~~~
-#
-# In this example, we enable the sensor support, then enable all types of sensors supported by the API, directly with `App` attributes
-# As a result, you get all type of SensorEvent (ASensorAccelerometer, ASensorMagneticField ...) in the `input` callback of `App`
 module sensors
 
-import android
+import game
 import mnit
 
 in "C header" `{
index 9726d15..1c40827 100644 (file)
@@ -256,9 +256,26 @@ end
 redef class CheckBox
        redef type NATIVE: Android_widget_CompoundButton
        redef var native do return (new Android_widget_CheckBox(app.native_activity)).new_global_ref
+       init do set_callback_on_toggle(native)
 
        redef fun is_checked do return native.is_checked
        redef fun is_checked=(value) do native.set_checked(value)
+
+       private fun on_toggle do notify_observers new ToggleEvent(self)
+
+       private fun set_callback_on_toggle(view: NATIVE)
+       import on_toggle in "Java" `{
+               final int final_sender_object = self;
+               CheckBox_incr_ref(final_sender_object);
+
+               view.setOnCheckedChangeListener(
+                       new android.widget.CompoundButton.OnCheckedChangeListener() {
+                               @Override
+                               public void onCheckedChanged(android.widget.CompoundButton buttonView, boolean isChecked) {
+                                       CheckBox_on_toggle(final_sender_object);
+                               }
+                       });
+       `}
 end
 
 redef class TextInput
index 42d90ae..7f22c78 100644 (file)
@@ -27,7 +27,6 @@ import app_base
 import core::error
 
 # Platform variations
-# TODO: move on the platform once qualified names are understand in the condition
 import linux::audio is conditional(linux)
 import android::audio is conditional(android)
 
index 4db4b22..ebebe83 100644 (file)
@@ -23,7 +23,6 @@ import app_base
 import serialization
 
 # Platform variations
-# TODO: move on the platform once qualified names are understand in the condition
 import linux::data_store is conditional(linux)
 import android::data_store is conditional(android)
 import ios::data_store is conditional(ios)
index 5f99576..0afe7aa 100644 (file)
@@ -18,9 +18,8 @@ module ui
 import app_base
 
 # Platform variations
-# TODO: move on the platform once qualified names are understand in the condition
 import linux::ui is conditional(linux)
-import android::ui is conditional(android) # FIXME it should be conditional to `android::platform`
+import android::ui is conditional(android)
 import ios::ui is conditional(ios)
 
 redef class App
@@ -241,12 +240,29 @@ class CheckBox
        var is_checked = false is writable
 end
 
+# Event sent from a `VIEW`
+class ViewEvent
+       super AppEvent
+
+       # The `VIEW` that raised this event
+       var sender: VIEW
+
+       # Type of the `sender`
+       type VIEW: View
+end
+
 # A `Button` press event
 class ButtonPressEvent
-       super AppEvent
+       super ViewEvent
+
+       redef type VIEW: Button
+end
+
+# The `CheckBox` `sender` has been toggled
+class ToggleEvent
+       super ViewEvent
 
-       # The `Button` that raised this event
-       var sender: Button
+       redef type VIEW: CheckBox
 end
 
 # A layout to visually organize `Control`s
index b7e8e61..2fa0bcb 100644 (file)
@@ -666,15 +666,15 @@ private class ASCIIFlatString
        end
 
        redef fun substring(from, count) do
+               var ln = _length
+               if count <= 0 then return ""
+               if (count + from) > ln then count = ln - from
                if count <= 0 then return ""
-
                if from < 0 then
                        count += from
-                       if count < 0 then return ""
+                       if count <= 0 then return ""
                        from = 0
                end
-               var ln = _length
-               if (count + from) > ln then count = ln - from
                return new ASCIIFlatString.full_data(_items, count, from + _first_byte, count)
        end
 
index 3997c4a..c412e9f 100644 (file)
@@ -21,7 +21,7 @@ module display_android is
        android_manifest """<uses-feature android:glEsVersion="0x00020000"/>"""
 end
 
-import ::android
+import ::android::game
 intrude import android::load_image
 
 private import gamnit::egl
index 875f739..acf3018 100644 (file)
@@ -25,16 +25,16 @@ in "ObjC" `{
 @interface NitCallbackReference: NSObject
 
        // Nit object target of the callbacks from UI events
-       @property (nonatomic) Button nit_button;
+       @property (nonatomic) View nit_view;
 
        // Actual callback method
-       -(void) nitOnEvent: (UIButton*) sender;
+       -(void) nitOnEvent: (UIView*) sender;
 @end
 
 @implementation NitCallbackReference
 
-       -(void) nitOnEvent: (UIButton*) sender {
-               Button_on_click(self.nit_button);
+       -(void) nitOnEvent: (UIView*) sender {
+               View_on_ios_event(self.nit_view);
        }
 @end
 
@@ -136,6 +136,8 @@ redef class View
        redef type NATIVE: UIView
 
        redef var enabled = null is lazy
+
+       private fun on_ios_event do end
 end
 
 redef class CompositeControl
@@ -260,7 +262,10 @@ redef class CheckBox
        # `UISwitch` acting as the real check box
        var ui_switch: UISwitch is noautoinit
 
-       init do
+       redef fun on_ios_event do notify_observers new ToggleEvent(self)
+
+       init
+       do
                # Tweak the layout so it is centered
                layout.native.distribution = new UIStackViewDistribution.fill_proportionally
                layout.native.alignment = new UIStackViewAlignment.center
@@ -269,6 +274,8 @@ redef class CheckBox
                var s = new UISwitch
                native.add_arranged_subview s
                ui_switch = s
+
+               ui_switch.set_callback self
        end
 
        redef fun text=(text) do lbl.text = text
@@ -278,6 +285,23 @@ redef class CheckBox
        redef fun is_checked=(value) do ui_switch.set_on_animated(value, true)
 end
 
+redef class UISwitch
+       # Register callbacks on this switch to be relayed to `sender`
+       private fun set_callback(sender: View)
+       import View.on_ios_event in "ObjC" `{
+
+               NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
+               ncr.nit_view = sender;
+
+               // Pin the objects in both Objective-C and Nit GC
+               View_incr_ref(sender);
+               ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
+
+               [self addTarget:ncr action:@selector(nitOnEvent:)
+                       forControlEvents:UIControlEventValueChanged];
+       `}
+end
+
 redef class TextInput
 
        redef type NATIVE: UITextField
@@ -300,25 +324,25 @@ redef class Button
 
        init do native.set_callback self
 
+       redef fun on_ios_event do notify_observers new ButtonPressEvent(self)
+
        redef fun text=(text) do if text != null then native.title = text.to_nsstring
        redef fun text do return native.current_title.to_s
 
-       private fun on_click do notify_observers new ButtonPressEvent(self)
-
        redef fun enabled=(enabled) do native.enabled = enabled or else true
        redef fun enabled do return native.enabled
 end
 
 redef class UIButton
        # Register callbacks on this button to be relayed to `sender`
-       private fun set_callback(sender: Button)
-       import Button.on_click in "ObjC" `{
+       private fun set_callback(sender: View)
+       import View.on_ios_event in "ObjC" `{
 
                NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
-               ncr.nit_button = sender;
+               ncr.nit_view = sender;
 
                // Pin the objects in both Objective-C and Nit GC
-               Button_incr_ref(sender);
+               View_incr_ref(sender);
                ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
 
                [self addTarget:ncr action:@selector(nitOnEvent:)
index 7c56e22..1e26f17 100644 (file)
@@ -317,6 +317,9 @@ redef class CheckBox
        redef type NATIVE: GtkCheckButton
        redef var native = new GtkCheckButton
 
+       redef fun signal(sender, data) do notify_observers new ToggleEvent(self)
+       init do native.signal_connect("toggled", self, null)
+
        redef fun text do return native.text
        redef fun text=(value) do native.text = (value or else "").to_s
 
index c8f02e3..e78da16 100644 (file)
@@ -22,7 +22,7 @@ module android_app is android_manifest_activity """
 
 import mnit
 import mnit::opengles1
-import ::android
+import ::android::game
 intrude import ::android::input_events
 
 in "C" `{
diff --git a/lib/postgresql/native_postgres.nit b/lib/postgresql/native_postgres.nit
new file mode 100644 (file)
index 0000000..4d22201
--- /dev/null
@@ -0,0 +1,142 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2015-2016 Guilherme Mansur <guilhermerpmansur@gmail.com>
+#
+# 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.
+
+module native_postgres is pkgconfig("libpq")
+
+in "C header" `{
+  #include <libpq-fe.h>
+`}
+
+extern class ExecStatusType `{int`}
+  new empty           `{ return PGRES_EMPTY_QUERY; `}
+  new command_ok      `{ return PGRES_COMMAND_OK; `}
+  new tuples_ok       `{ return PGRES_TUPLES_OK; `}
+  new copy_out        `{ return PGRES_COPY_OUT; `}
+  new copy_in         `{ return PGRES_COPY_IN; `}
+  new bad_response    `{ return PGRES_BAD_RESPONSE; `}
+  new nonfatal_error  `{ return PGRES_NONFATAL_ERROR; `}
+  new fatal_error     `{ return PGRES_FATAL_ERROR; `}
+
+  fun is_ok: Bool `{return self == PGRES_TUPLES_OK || self == PGRES_COMMAND_OK; `}
+
+  redef fun to_s import NativeString.to_s `{
+    char * err = PQresStatus(self);
+    if(err == NULL) err = "";
+    return NativeString_to_s(err);
+  `}
+end
+
+extern class ConnStatusType `{int`}
+  new connection_ok `{ return CONNECTION_OK; `}
+  new connection_bad `{ return CONNECTION_BAD; `}
+
+  fun is_ok: Bool `{return self == CONNECTION_OK; `}
+end
+
+extern class PGResult `{PGresult *`}
+  # Frees the memory block associated with the result
+  fun clear `{PQclear(self); `}
+
+  # Returns the number of rows in the query result
+  fun ntuples:Int `{ return PQntuples(self); `}
+
+  # Returns the number of columns in each row of the query result
+  fun nfields:Int `{return PQnfields(self); `}
+
+  # Returns the ExecStatusType of a result
+  fun status: ExecStatusType `{ return PQresultStatus(self); `}
+
+  # Returns the field name of a given column_number
+  fun fname(column_number:Int):String import NativeString.to_s `{
+    return NativeString_to_s( PQfname(self, column_number));
+  `}
+
+  # Returns the column number associated with the column name
+  fun fnumber(column_name:String):Int import String.to_cstring `{
+    return PQfnumber(self, String_to_cstring(column_name));
+  `}
+
+  # Returns a single field value of one row of the result at row_number, column_number
+  fun value(row_number:Int, column_number:Int):String import NativeString.to_s `{
+    return NativeString_to_s(PQgetvalue(self, row_number, column_number));
+  `}
+
+  # Tests wether a field is a null value
+  fun is_null(row_number:Int, column_number: Int): Bool `{
+    return PQgetisnull(self, row_number, column_number);
+  `}
+
+end
+extern class NativePostgres `{PGconn *`}
+
+  # Connect to a new database using the conninfo string as a parameter
+  new connectdb(conninfo: String) import String.to_cstring `{
+    PGconn * self = NULL;
+    self = PQconnectdb(String_to_cstring(conninfo));
+    return self;
+  `}
+
+  # Submits a query to the server and waits for the result returns the ExecStatustype of the query
+  fun exec(query: String): PGResult import String.to_cstring `{
+    PGresult *res = PQexec(self, String_to_cstring(query));
+    return res;
+  `}
+
+  # Prepares a statement with the given parameters
+  fun prepare(stmt: String, query: String, nParams: Int):PGResult import String.to_cstring `{
+    const char * stmtName = String_to_cstring(stmt);
+    const char * queryStr = String_to_cstring(query);
+    PGresult * res = PQprepare(self, stmtName, queryStr, nParams, NULL);
+    return res;
+  `}
+
+  fun exec_prepared(stmt: String, nParams: Int, values: Array[String], pLengths: Array[Int], pFormats: Array[Int], resultFormat: Int):PGResult import String.to_cstring, Array[String].[], Array[Int].[] `{
+    const char * stmtName = String_to_cstring(stmt);
+    const char * paramValues[nParams];
+    int paramLengths[nParams];
+    int paramFormats[nParams];
+    int i;
+    for(i = 0; i < nParams; i++)
+      paramValues[i] = String_to_cstring(Array_of_String__index(values, i));
+    for(i = 0; i < nParams; i++)
+      paramLengths[i] = Array_of_Int__index(pLengths, i);
+    for(i = 0; i < nParams; i++)
+      paramFormats[i] = Array_of_Int__index(pFormats, i);
+    PGresult * res = PQexecPrepared(self, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat);
+    return res;
+  `}
+
+  # Returns the error message of the last operation on the connection
+  fun error: String import NativeString.to_s `{
+    char * error = PQerrorMessage(self);
+    return NativeString_to_s(error);
+  `}
+
+  # Returns the status of this connection
+  fun status: ConnStatusType `{
+    return PQstatus(self);
+  `}
+
+  # Closes the connection to the server
+  fun finish  `{
+    PQfinish(self);
+  `}
+
+  # Closes the connection to the server and attempts to reconnect with the previously used params
+  fun reset `{
+    PQreset(self);
+  `}
+end
diff --git a/lib/postgresql/package.ini b/lib/postgresql/package.ini
new file mode 100644 (file)
index 0000000..dc82d85
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=postgresql
+tags=database,lib
+maintainer=Guilherme Mansur <guilhermerpmansur@gmail.com>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/postgresql/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/postgresql/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
\ No newline at end of file
index 169335a..c644c2e 100644 (file)
@@ -1,7 +1,7 @@
 [package]
 name=rubix
 tags=algo,lib
-maintainer=Lucas Bajolet<lucas.bajolet@hotmail.com>
+maintainer=Lucas Bajolet<r4pass@hotmail.com>
 license=Apache-2.0
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/lib/rubix.nit
index e01dbd2..4f9ad61 100644 (file)
@@ -118,6 +118,15 @@ Finally, standard markdown documents can be checked with:
 
     $ nitunit foo.md
 
+When testing, the environment variable `NIT_TESTING` is set to `true`.
+This flag can be used by libraries and program to prevent (or limit) the execution of dangerous pieces of code.
+
+~~~~~
+# NIT_TESTING is automatically set.
+#
+#     assert "NIT_TESTING".environ == "true"
+~~~~
+
 ## Working with `TestSuites`
 
 TestSuites are Nit files that define a set of TestCases for a particular module.
index 65c1c62..2590b0d 100644 (file)
@@ -31,8 +31,38 @@ module model_collect
 
 import model_views
 
+redef class MEntity
+
+       # Collect modifier keywords like `redef`, `private` etc.
+       fun collect_modifiers: Array[String] do
+               return new Array[String]
+       end
+end
+
+redef class MPackage
+       redef fun collect_modifiers do
+               var res = super
+               res.add "package"
+               return res
+       end
+end
+
+redef class MGroup
+       redef fun collect_modifiers do
+               var res = super
+               res.add "group"
+               return res
+       end
+end
+
 redef class MModule
 
+       redef fun collect_modifiers do
+               var res = super
+               res.add "module"
+               return res
+       end
+
        # Collect all transitive imports.
        fun collect_ancestors(view: ModelView): Set[MModule] do
                var res = new HashSet[MModule]
@@ -133,6 +163,8 @@ end
 
 redef class MClass
 
+       redef fun collect_modifiers do return intro.collect_modifiers
+
        # Collect direct parents of `self` with `visibility >= to min_visibility`.
        fun collect_parents(view: ModelView): Set[MClass] do
                var res = new HashSet[MClass]
@@ -404,9 +436,8 @@ redef class MClassDef
                return res
        end
 
-       # Collect modifiers like redef, private etc.
-       fun collect_modifiers: Array[String] do
-               var res = new Array[String]
+       redef fun collect_modifiers do
+               var res = super
                if not is_intro then
                        res.add "redef"
                else
@@ -417,10 +448,13 @@ redef class MClassDef
        end
 end
 
+redef class MProperty
+       redef fun collect_modifiers do return intro.collect_modifiers
+end
+
 redef class MPropDef
-       # Collect modifiers like redef, private, abstract, intern, fun etc.
-       fun collect_modifiers: Array[String] do
-               var res = new Array[String]
+       redef fun collect_modifiers do
+               var res = super
                if not is_intro then
                        res.add "redef"
                else
@@ -440,6 +474,8 @@ redef class MPropDef
                        else
                                res.add "fun"
                        end
+               else if mprop isa MAttributeDef then
+                       res.add "var"
                end
                return res
        end
diff --git a/src/model/model_json.nit b/src/model/model_json.nit
new file mode 100644 (file)
index 0000000..fc4b101
--- /dev/null
@@ -0,0 +1,304 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Make model entities Jsonable.
+#
+# To avoid cycles, every reference from a MEntity to another is replaced by a
+# MEntityRef.
+#
+# How subobjects are retrieved using the MEntityRef is the responsability of the
+# client. Json objects can be returned as this or inflated with concrete objet
+# rather than the refs.
+#
+# TODO consider serialization module?
+module model_json
+
+import model::model_collect
+import json
+import loader
+
+# A reference to another mentity.
+class MEntityRef
+       super MEntity
+
+       # MEntity to link to.
+       var mentity: MEntity
+
+       # Return `self` as a Json Object.
+       #
+       # By default, MEntity references contain only the `full_name` of the Mentity.
+       # You should redefine this method in your client to implement a different behavior.
+       redef fun json do
+               var obj = new JsonObject
+               obj["full_name"] = mentity.full_name
+               return obj
+       end
+end
+
+redef class MEntity
+       super Jsonable
+
+       # Return `self` as a JsonObject.
+       #
+       # By default, every reference to another MEntity is replaced by a pointer
+       # to the MEntity::json_id.
+       fun json: JsonObject do
+               var obj = new JsonObject
+               obj["name"] = name
+               obj["class_name"] = class_name
+               obj["full_name"] = full_name
+               obj["mdoc"] = mdoc_or_fallback
+               var modifiers = new JsonArray
+               for modifier in collect_modifiers do
+                       modifiers.add modifier
+               end
+               obj["modifiers"] = modifiers
+               return obj
+       end
+
+       redef fun to_json do return json.to_json
+end
+
+redef class MDoc
+       super Jsonable
+
+       # Return `self` as a JsonObject.
+       fun json: JsonObject do
+               var obj = new JsonObject
+               obj["content"] = content.join("\n")
+               obj["location"] = location
+               return obj
+       end
+
+       redef fun to_json do return json.to_json
+end
+
+redef class Location
+       super Jsonable
+
+       # Return `self` as a JsonObject.
+       fun json: JsonObject do
+               var obj = new JsonObject
+               obj["column_end"] = column_end
+               obj["column_start"] = column_start
+               obj["line_end"] = line_end
+               obj["line_start"] = line_start
+               var file = self.file
+               if file != null then
+                       obj["file"] = file.filename
+               end
+               return obj
+       end
+
+       redef fun to_json do return json.to_json
+end
+
+redef class MVisibility
+       super Jsonable
+
+       redef fun to_json do return to_s.to_json
+end
+
+redef class MPackage
+
+       redef fun json do
+               var obj = super
+               obj["visibility"] = public_visibility
+               if ini != null then
+                       obj["ini"] = new JsonObject.from(ini.as(not null).to_map)
+               end
+               obj["root"] = to_mentity_ref(root)
+               obj["mgroups"] = to_mentity_refs(mgroups)
+               return obj
+       end
+end
+
+redef class MGroup
+       redef fun json do
+               var obj = super
+               obj["visibility"] = public_visibility
+               obj["is_root"] = is_root
+               obj["mpackage"] = to_mentity_ref(mpackage)
+               obj["default_mmodule"] = to_mentity_ref(default_mmodule)
+               obj["parent"] = to_mentity_ref(parent)
+               obj["mmodules"] = to_mentity_refs(mmodules)
+               obj["mgroups"] = to_mentity_refs(in_nesting.direct_smallers)
+               return obj
+       end
+end
+
+redef class MModule
+       redef fun json do
+               var obj = super
+               obj["location"] = location
+               obj["visibility"] = public_visibility
+               obj["mpackage"] = to_mentity_ref(mpackage)
+               obj["mgroup"] = to_mentity_ref(mgroup)
+               obj["intro_mclasses"] = to_mentity_refs(intro_mclasses)
+               obj["mclassdefs"] = to_mentity_refs(mclassdefs)
+               return obj
+       end
+end
+
+redef class MClass
+       redef fun json do
+               var obj = super
+               obj["visibility"] = visibility
+               var arr = new JsonArray
+               for mparameter in mparameters do arr.add mparameter
+               obj["mparameters"] = arr
+               obj["intro"] = to_mentity_ref(intro)
+               obj["intro_mmodule"] = to_mentity_ref(intro_mmodule)
+               obj["mpackage"] = to_mentity_ref(intro_mmodule.mpackage)
+               obj["mclassdefs"] = to_mentity_refs(mclassdefs)
+               return obj
+       end
+end
+
+redef class MClassDef
+       redef fun json do
+               var obj = super
+               obj["visibility"] = mclass.visibility
+               obj["location"] = location
+               obj["is_intro"] = is_intro
+               var arr = new JsonArray
+               for mparameter in mclass.mparameters do arr.add mparameter
+               obj["mparameters"] = arr
+               obj["mmodule"] = to_mentity_ref(mmodule)
+               obj["mclass"] = to_mentity_ref(mclass)
+               obj["mpropdefs"] = to_mentity_refs(mpropdefs)
+               obj["intro_mproperties"] = to_mentity_refs(intro_mproperties)
+               return obj
+       end
+end
+
+redef class MProperty
+       redef fun json do
+               var obj = super
+               obj["visibility"] = visibility
+               obj["intro"] = to_mentity_ref(intro)
+               obj["intro_mclassdef"] = to_mentity_ref(intro_mclassdef)
+               obj["mpropdefs"] = to_mentity_refs(mpropdefs)
+               return obj
+       end
+end
+
+redef class MMethod
+       redef fun json do
+               var obj = super
+               obj["is_init"] = is_init
+               obj["msignature"] = intro.msignature
+               return obj
+       end
+end
+
+redef class MAttribute
+       redef fun json do
+               var obj = super
+               obj["static_mtype"] = to_mentity_ref(intro.static_mtype)
+               return obj
+       end
+end
+
+redef class MVirtualTypeProp
+       redef fun json do
+               var obj = super
+               obj["mvirtualtype"] = to_mentity_ref(mvirtualtype)
+               obj["bound"] = to_mentity_ref(intro.bound)
+               return obj
+       end
+end
+
+redef class MPropDef
+       redef fun json do
+               var obj = super
+               obj["visibility"] = mproperty.visibility
+               obj["location"] = location
+               obj["is_intro"] = is_intro
+               obj["mclassdef"] = to_mentity_ref(mclassdef)
+               obj["mproperty"] = to_mentity_ref(mproperty)
+               return obj
+       end
+end
+
+redef class MMethodDef
+       redef fun json do
+               var obj = super
+               obj["msignature"] = msignature
+               return obj
+       end
+end
+
+redef class MAttributeDef
+       redef fun json do
+               var obj = super
+               obj["static_mtype"] = to_mentity_ref(static_mtype)
+               return obj
+       end
+end
+
+redef class MVirtualTypeDef
+       redef fun json do
+               var obj = super
+               obj["bound"] = to_mentity_ref(bound)
+               obj["is_fixed"] = is_fixed
+               return obj
+       end
+end
+
+redef class MSignature
+       redef fun json do
+               var obj = new JsonObject
+               obj["arity"] = arity
+               var arr = new JsonArray
+               for mparam in mparameters do arr.add mparam
+               obj["mparams"] = arr
+               obj["return_mtype"] = to_mentity_ref(return_mtype)
+               obj["vararg_rank"] = vararg_rank
+               return obj
+       end
+end
+
+redef class MParameterType
+       redef fun json do
+               var obj = new JsonObject
+               obj["name"] = name
+               obj["rank"] = rank
+               obj["mtype"] = to_mentity_ref(mclass.intro.bound_mtype.arguments[rank])
+               return obj
+       end
+end
+
+redef class MParameter
+       redef fun json do
+               var obj = new JsonObject
+               obj["is_vararg"] = is_vararg
+               obj["name"] = name
+               obj["mtype"] = to_mentity_ref(mtype)
+               return obj
+       end
+end
+
+# Create a ref to a `mentity`.
+fun to_mentity_ref(mentity: nullable MEntity): nullable MEntityRef do
+       if mentity == null then return null
+       return new MEntityRef(mentity)
+end
+
+# Return a collection of `mentities` as a JsonArray of MEntityRefs.
+fun to_mentity_refs(mentities: Collection[MEntity]): JsonArray do
+       var array = new JsonArray
+       for mentity in mentities do array.add to_mentity_ref(mentity)
+       return array
+end
index 87d3c81..246e813 100644 (file)
@@ -836,9 +836,7 @@ redef class AMethPropdef
                                return
                        end
                else
-                       if mprop.is_broken then
-                               return
-                       end
+                       if mprop.is_broken then return
                        if not self.check_redef_keyword(modelbuilder, mclassdef, n_kwredef, not self isa AMainMethPropdef, mprop) then return
                        check_redef_property_visibility(modelbuilder, self.n_visibility, mprop)
                end
@@ -1197,8 +1195,12 @@ redef class AAttrPropdef
                if mreadprop == null then
                        var mvisibility = new_property_visibility(modelbuilder, mclassdef, self.n_visibility)
                        mreadprop = new MMethod(mclassdef, readname, self.location, mvisibility)
-                       if not self.check_redef_keyword(modelbuilder, mclassdef, n_kwredef, false, mreadprop) then return
+                       if not self.check_redef_keyword(modelbuilder, mclassdef, n_kwredef, false, mreadprop) then
+                               mreadprop.is_broken = true
+                               return
+                       end
                else
+                       if mreadprop.is_broken then return
                        if not self.check_redef_keyword(modelbuilder, mclassdef, n_kwredef, true, mreadprop) then return
                        check_redef_property_visibility(modelbuilder, self.n_visibility, mreadprop)
                end
@@ -1296,9 +1298,13 @@ redef class AAttrPropdef
                                if mvisibility > protected_visibility then mvisibility = protected_visibility
                        end
                        mwriteprop = new MMethod(mclassdef, writename, self.location, mvisibility)
-                       if not self.check_redef_keyword(modelbuilder, mclassdef, nwkwredef, false, mwriteprop) then return
+                       if not self.check_redef_keyword(modelbuilder, mclassdef, nwkwredef, false, mwriteprop) then
+                               mwriteprop.is_broken = true
+                               return
+                       end
                        mwriteprop.deprecation = mreadprop.deprecation
                else
+                       if mwriteprop.is_broken then return
                        if not self.check_redef_keyword(modelbuilder, mclassdef, nwkwredef or else n_kwredef, true, mwriteprop) then return
                        if atwritable != null then
                                check_redef_property_visibility(modelbuilder, atwritable.n_visibility, mwriteprop)
@@ -1608,6 +1614,7 @@ redef class ATypePropdef
                                break
                        end
                else
+                       if mprop.is_broken then return
                        assert mprop isa MVirtualTypeProp
                        check_redef_property_visibility(modelbuilder, self.n_visibility, mprop)
                end
index 165ea13..176ec5f 100644 (file)
@@ -41,7 +41,7 @@ var args = toolcontext.option_context.rest
 var mmodules = modelbuilder.parse_full(args)
 modelbuilder.run_phases
 
-if opt_full.value then mmodules = model.mmodules
+if opt_full.value then mmodules = modelbuilder.parsed_modules
 
 var dir = opt_dir.value
 if dir != null then
index 522591d..cc9ea85 100644 (file)
@@ -63,6 +63,8 @@ if toolcontext.opt_gen_unit.value then
        exit(0)
 end
 
+"NIT_TESTING".setenv("true")
+
 var page = new HTMLTag("testsuites")
 
 if toolcontext.opt_full.value then mmodules = model.mmodules
diff --git a/tests/base_redef.nit b/tests/base_redef.nit
new file mode 100644 (file)
index 0000000..d42bfec
--- /dev/null
@@ -0,0 +1,65 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+import core::kernel
+
+class A
+       fun f do 1.output
+       fun f2: Int do return 2
+       fun f2=(i:Int) do i.output
+       fun f3=(i:Int) do i.output
+       var v = 4
+       type T: A
+       fun t(t: T): T do return t
+end
+
+class B
+       super A#alt1#
+       redef fun f do 10.output
+       redef var f2 = 20
+       var f3 = 30 is redef writable
+       redef var v = 40
+       redef type T: B
+end
+
+class C
+       super B#alt2#
+       redef fun f do 100.output
+       redef var f2 = 200
+       redef var f3 = 300
+       redef var v = 400
+       redef type T: C
+
+end
+
+var a = new A
+a.f
+a.f2 = -2
+a.f2.output
+a.f3 = -3
+a.t(a).f
+
+a = new B
+a.f
+a.f2 = -2
+a.f2.output
+a.f3 = -3
+a.t(a).f
+
+a = new C
+a.f
+a.f2 = -2
+a.f2.output
+a.f3 = -3
+a.t(a).f
diff --git a/tests/sav/base_redef.res b/tests/sav/base_redef.res
new file mode 100644 (file)
index 0000000..3b792d6
--- /dev/null
@@ -0,0 +1,11 @@
+1
+-2
+2
+-3
+1
+10
+-2
+10
+100
+-2
+100
diff --git a/tests/sav/base_redef_alt1.res b/tests/sav/base_redef_alt1.res
new file mode 100644 (file)
index 0000000..50e360c
--- /dev/null
@@ -0,0 +1,5 @@
+alt/base_redef_alt1.nit:29,12: Error: no property `B::f` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt1.nit:30,12--13: Error: no property `B::f2` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt1.nit:31,6--7: Error: no property `B::f3=` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt1.nit:32,12: Error: no property `B::v` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt1.nit:33,2--16: Error: no property `B::T` is inherited. Remove the `redef` keyword to define a new property.
diff --git a/tests/sav/base_redef_alt2.res b/tests/sav/base_redef_alt2.res
new file mode 100644 (file)
index 0000000..1c263c4
--- /dev/null
@@ -0,0 +1,5 @@
+alt/base_redef_alt2.nit:38,12: Error: no property `C::f` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt2.nit:39,12--13: Error: no property `C::f2` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt2.nit:40,12--13: Error: no property `C::f3` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt2.nit:41,12: Error: no property `C::v` is inherited. Remove the `redef` keyword to define a new property.
+alt/base_redef_alt2.nit:42,2--16: Error: no property `C::T` is inherited. Remove the `redef` keyword to define a new property.
diff --git a/tests/sav/test_postgres_native.res b/tests/sav/test_postgres_native.res
new file mode 100644 (file)
index 0000000..0c511fe
--- /dev/null
@@ -0,0 +1,3 @@
+aname   class   sex   
+Whale   mammal   1   
+Snake   reptile   0   
index f40c047..54cb7f8 100644 (file)
@@ -43,3 +43,6 @@ test.has_suffix("test") => true
 test.has_suffix("bt") => false
 test.has_suffix("bat") => false
 test.has_suffix("foot") => false
+........
+
+........
diff --git a/tests/test_postgres_native.nit b/tests/test_postgres_native.nit
new file mode 100644 (file)
index 0000000..ff3be6f
--- /dev/null
@@ -0,0 +1,73 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2016 Guilherme Mansur <guilhermerpmansur@gmail.com>
+#
+# 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.
+
+module test_postgres_native
+
+import postgresql::native_postgres
+
+var db = new NativePostgres.connectdb("dbname=postgres")
+assert postgres_open: db.status.is_ok else print_error db.error
+
+var result = db.exec("CREATE TABLE IF NOT EXISTS animals (aname TEXT PRIMARY KEY, class TEXT NOT NULL, sex INTEGER)")
+assert postgres_create_table: result.status.is_ok else print_error db.error
+
+result = db.exec("INSERT INTO animals VALUES('Whale', 'mammal', 1)")
+assert postgres_insert_1: result.status.is_ok else print_error db.error
+
+result = db.exec("INSERT INTO animals VALUES('Snake', 'reptile', 0)")
+assert postgres_insert_2: result.status.is_ok else print_error db.error
+
+result = db.exec("SELECT * FROM animals")
+assert postgres_select: result.status.is_ok else print_error db.error
+
+assert postgres_ntuples: result.ntuples == 2 else print_error db.error
+assert postgres_nfields: result.nfields == 3 else print_error db.error
+assert postgres_fname: result.fname(0) == "aname" else print_error db.error
+assert postgres_isnull: result.is_null(0,0) == false else print_error db.error
+assert postgres_value: result.value(0,0) == "Whale" else print_error db.error
+
+var cols: Int = result.nfields
+var rows: Int = result.ntuples
+var fields: String = ""
+for c in [0..cols[ do fields += result.fname(c) + "   "
+print fields
+for i in [0..rows[ do
+  fields = ""
+  for j in [0..cols[ do fields +=  result.value(i, j) + "   "
+  print fields
+end
+
+result = db.exec("DELETE FROM animals WHERE aname = 'Lioness'")
+assert postgres_delete_1: result.status.is_ok else print_error db.error
+
+result = db.exec("DELETE FROM animals WHERE aname = 'Snake'")
+assert postgres_delete_2: result.status.is_ok else print_error db.error
+
+result = db.prepare("PREPARED_INSERT", "INSERT INTO animals(aname, class, sex) VALUES ($1, $2, $3)", 3)
+assert postgres_prepare: result.status.is_ok else print_error db.error
+
+result = db.exec("DELETE FROM animals WHERE aname = 'Frog'")
+assert postgres_delete_3: result.status.is_ok else print_error db.error
+
+var values = ["Frog", "Anphibian", "1"]
+var lengths = [values[0].length, values[1].length, values[2].length]
+var formats = [0,0,0]
+result = db.exec_prepared("PREPARED_INSERT", 3, values, lengths, formats,0)
+assert postgres_exec_prepared: result.status.is_ok else print_error db.error
+
+result = db.exec("DROP TABLE animals")
+assert postgres_drop_table: result.status.is_ok else print_error db.error
+db.finish
index c2393aa..ed7d73d 100644 (file)
@@ -75,3 +75,6 @@ print("test.has_suffix(\"bt\") => {test.has_suffix("bt")}")
 print("test.has_suffix(\"bat\") => {test.has_suffix("bat")}")
 print("test.has_suffix(\"foot\") => {test.has_suffix("foot")}")
 
+print "........"
+print "/".substring(7, 1)
+print "........"