Merge: Use linker symbols to encode colors
authorJean Privat <jean@pryen.org>
Tue, 13 Jan 2015 01:29:02 +0000 (20:29 -0500)
committerJean Privat <jean@pryen.org>
Tue, 13 Jan 2015 01:29:02 +0000 (20:29 -0500)
Genuine constant static variables are used to store the colors used in OO mechanisms.
This makes the compiler program slower since additional indirections are required to get the values.
It also produces a little bit larger executables since static memory has to store the colors.

This PR introduce a trick user in the original PRM that uses linker symbols to encode the colors.
It is not completely portable so it must be activated with the option `--colors-are-symbols`

For numbers (with the traditional nitc/nitc/nitc)
before: 0m7.544s
after: 0m7.228s (so -4%)

Pull-Request: #1093
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

44 files changed:
contrib/jwrapper/Makefile
contrib/jwrapper/README.md
contrib/jwrapper/grammar/javap.sablecc
contrib/jwrapper/src/code_generator.nit
contrib/jwrapper/src/javap_visitor.nit
contrib/jwrapper/src/model.nit [moved from contrib/jwrapper/src/types.nit with 92% similarity]
contrib/nitiwiki/src/wiki_base.nit
contrib/nitiwiki/src/wiki_html.nit
contrib/pep8analysis/src/pep8analysis_web.nit
lib/android/README.md [new file with mode: 0644]
lib/android/android.nit
lib/array_debug.nit
lib/bucketed_game.nit
lib/curl/curl.nit
lib/filter_stream.nit
lib/github/api.nit
lib/ini.nit
lib/json/static.nit
lib/neo4j/graph/graph.nit [new file with mode: 0644]
lib/neo4j/graph/json_graph_store.nit [new file with mode: 0644]
lib/neo4j/graph/sequential_id.nit [new file with mode: 0644]
lib/neo4j/json_store.nit [deleted file]
lib/nitcc_runtime.nit
lib/opts.nit
lib/pipeline.nit
lib/progression.nit [new file with mode: 0644]
lib/scene2d.nit
lib/socket/socket.nit
lib/socket/socket_c.nit
lib/standard/gc.nit
lib/standard/stream.nit
lib/string_experimentations/utf8.nit
lib/symbol.nit
src/compiler/abstract_compiler.nit
src/compiler/android_platform.nit
src/compiler/global_compiler.nit
src/compiler/separate_compiler.nit
src/doc/doc_pages.nit
src/loader.nit
src/model/mmodule.nit
src/modelize/modelize_class.nit
src/modelize/modelize_property.nit
src/test_docdown.nit
src/toolcontext.nit

index b554f0d..0932498 100644 (file)
@@ -1,14 +1,22 @@
-default:
-       mkdir -p bin
+all: nitcc grammar bin/jwrapper
+
+nitcc:
        make -C ../nitcc
-       ../nitcc/src/nitcc ./grammar/javap.sablecc
-       ../../bin/nitc ./src/jwrapper.nit -o ./bin/jwrapper
-       mv *.nit ./src/
-       mkdir -p gen
-       mv javap* ./gen/
+
+grammar:
+       ../nitcc/src/nitcc grammar/javap.sablecc
+       mkdir -p src gen
+       mv *.nit src/
+       mv javap* gen/
+
+bin/jwrapper:
+       mkdir -p bin
+       ../../bin/nitc src/jwrapper.nit -o bin/jwrapper
 
 clean:
        rm -f bin/javap_test_parser bin/jwrapper
        rm -f gen/*
        rm -rf .nit_compile/
        rm src/javap_lexer.nit src/javap_parser.nit src/javap_test_parser.nit
+
+.PHONY: grammar bin/jwrapper
index 412392e..da33dd2 100644 (file)
@@ -1,20 +1,23 @@
-# JWRAPPER : Extern classes generator from java .class
-## Description
-jwrapper is a code generator that creates Nit extern classes `in "Java"` from .class files.
+_jwrapper_, an extern class generator from Java class files
 
-## Installation
-jwrapper is designed to be installed from the `contrib` directory of Nit repository. (http://www.nitlanguage.org)
+# Description
+_jwrapper_ automates wrapping Java classes so they can be accessed from Nit code. It generates Nit extern classes by analyzing Java class files.
+
+_jwrapper_ reuse Nit types of already wrapped Java classes by searching in the `lib/java` and `lib/android` folders in the Nit repository. It won't wrap a class that are already is those folders.
+
+# Installation
+_jwrapper_ is designed to be installed from the `contrib` directory of Nit repository. (http://www.nitlanguage.org)
 
 To initiate installation process, use `make` in the `contrib/jwrapper` directory.
 
-jwrapper relies on `nitcc` that will be automatically compiled from `contrib/nitcc`.
+_jwrapper_ relies on `nitcc` that will be automatically compiled from `contrib/nitcc`.
 
-## Usage
-The jwrapper binary can be found under `jwrapper/bin` directory.
+# Usage
+The _jwrapper_ binary can be found under `contrib/jwrapper/bin` directory.
 
-Since jwrapper uses `grep` to find existing libraries, make sure that the environment variable `NIT_DIR` is properly set to your Nit root directory.
+_jwrapper_ uses `grep` to find existing libraries, make sure that the environment variable `NIT_DIR` is properly set to your Nit root directory.
 
-Since jwrapper uses `javap` to extract data from .class files, the JDK7 or higher has to be installed and must be in your `$PATH`. (Older versions of `javap` do not show generic signatures)
+_jwrapper_ uses `javap` to extract data from .class files, the JDK7 or higher has to be installed and must be in your `$PATH`. (Older versions of `javap` do not show generic signatures)
 
 Usage :
 
@@ -28,7 +31,7 @@ The options are :
 
 `-c, --comment`
 
-* When a method contains at least one unknown type, the code generator will comment the whole method and let the client manage it.
+* When a method contains at least one unknown type, the code generator will comment the whole method and let the client manage it. Unknown types are types that doesn't have an equivalent in Nit as of yet.
 
 `-w, --wrap`
 
@@ -38,8 +41,4 @@ The options are :
 
 * Print the help message
 
-Unknown types are types that doesn't have an equivalent in Nit as of yet.
-
-Jwrapper won't wrap a class that already is in the `lib/android` directory.
-
 Can't use both -c and -w together, either you comment unknown types or you wrap them.
index 4e9165a..87dfa12 100644 (file)
@@ -13,12 +13,12 @@ multi_files = class_or_interface*;
 
 class_or_interface = class_declaration | interface_declaration;
 
-class_declaration = class_header '{' field_declaration* '}';
+class_declaration = class_header '{' property_declaration* '}';
 
 class_header = modifier* 'class' full_class_name extends_declaration?
                           implements_declaration? throws_declaration?;
 interface_declaration = modifier* 'interface' full_class_name extends_interface_declaration?
-                                               '{' field_declaration* '}';
+                                               '{' property_declaration* '}';
 
 modifier = 'public'|'private'|'protected'|'static'|'final'|'native'|'synchronized'|'abstract'|'threadsafe'|'transient'|'volatile';
 type = type_specifier '[]'*;
@@ -50,7 +50,7 @@ statement_block = '{' statement* '}';
 variable_id = identifier '[]'*;
 method_id = identifier;
 
-field_declaration = method_declaration | constructor_declaration | variable_declaration | static_declaration | ';';
+property_declaration = method_declaration | constructor_declaration | variable_declaration | static_declaration | ';';
 variable_declaration = modifier* type variable_id throws_declaration? ';';
 method_declaration = modifier* generic_param? type method_id '(' parameter_list? ')' throws_declaration? ';';
 constructor_declaration = modifier* full_class_name '(' parameter_list? ')' throws_declaration? ';';
index 9e997cc..0da3c77 100644 (file)
@@ -17,7 +17,7 @@
 # Services to generate extern class `in "Java"`
 module code_generator
 
-intrude import types
+intrude import model
 
 class CodeGenerator
 
@@ -128,9 +128,9 @@ class CodeGenerator
 
        fun gen_attribute(jid: String, jtype: JavaType): String
        do
-               return "\tvar {jid.to_snake_case}: {jtype.to_nit_type}\n"
+               return "\tvar {jid.to_nit_method_name}: {jtype.to_nit_type}\n"
        end
-       
+
        fun gen_method(jmethod_id: String, nmethod_id: String, jreturn_type: JavaType, jparam_list: Array[JavaType]): String
        do
                var java_params = ""
@@ -138,7 +138,7 @@ class CodeGenerator
                var nit_id = "arg"
                var nit_id_no = 0
                var nit_types = new Array[NitType]
-               var comment = "" 
+               var comment = ""
 
                # Parameters
                for i in [0..jparam_list.length[ do
@@ -177,7 +177,7 @@ class CodeGenerator
                end
 
                # Method identifier
-               var method_id = nmethod_id.to_snake_case
+               var method_id = nmethod_id.to_nit_method_name
                var nit_signature = new Array[String]
 
                nit_signature.add "\tfun {method_id}"
@@ -216,7 +216,7 @@ class CodeGenerator
                        temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
                # Methods with return type
                else if return_type != null then
-                       temp.add(" in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast} recv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
+                       temp.add(" in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}recv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
                # Methods without return type
                else if jreturn_type.is_void then
                        temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
@@ -251,48 +251,29 @@ class CodeWarehouse
                else
                        imports = """ import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item"""
                end
-               
+
                return imports
        end
+end
 
-       private fun create_loop(java_type: JavaType, nit_type: NitType, is_param: Bool, jarray_id, narray_id: String): String
+redef class String
+       # Convert the Java method name `self` to the Nit style
+       #
+       # * Converts to snake case
+       # * Strips `Get` and `Set`
+       # * Add suffix `=` to setters
+       fun to_nit_method_name: String
        do
-               var loop_header = ""
-               var loop_body = ""
-               var gen_type = nit_type.generic_params.join("_")
-
-               if is_param then
-                       if java_type.is_primitive_array then
-                               loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
-                               loop_body   = """\t\t\t{{{jarray_id}}}[i] = {{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{nit_type.arg_id}}}, i);"""
-                       else if nit_type.id == "Array" then
-                               loop_header = """int length = Array_of_{{{gen_type}}}_length((int){{{nit_type.arg_id}}});\n\t\tfor(int i=0; i < length; ++i)"""
-                               loop_body   = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{narray_id}}}, i));"""
-                       else
-                               loop_header = """int itr = {{{nit_type.id}}}_of_{{{gen_type}}}_iterator({{{nit_type.arg_id}}});\n\t\twhile(Iterator_of_{{{gen_type}}}_is_ok(itr)) {"""
-                               if nit_type.is_map then
-                                       var key_cast = java_type.to_cast(java_type.generic_params[0].id, true)
-                                       var value_cast = java_type.to_cast(java_type.generic_params[1].id, true)
-                                       loop_body   = """\t\t\t{{{jarray_id}}}[{{{key_cast}}}iterator_of_{{{nit_type.id}}}_key(itr)] = {{{value_cast}}}iterator_of_{{{nit_type.id}}}_item(itr);\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
-                               else
-                                       loop_body   = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}iterator_of_{{{nit_type.id}}}_item(itr));\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
-                               end
-                       end
+               var name
+               if self.has_prefix("Get") then
+                       name = self.substring_from(3)
+               else if self.has_prefix("Set") then
+                       name = self.substring_from(3)
+                       name += "="
                else
-                       if nit_type.is_map then
-                               var key_cast = java_type.to_cast(java_type.generic_params[0].id, false)
-                               var value_cast = java_type.to_cast(java_type.generic_params[1].id, false)
-                               loop_header = """for (java.util.Map.Entry<{{{java_type.generic_params[0]}}}, {{{java_type.generic_params[1]}}}> e: {{{jarray_id}}})"""
-                               loop_body   = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_{{{nit_type.generic_params[1]}}}__index_assign({{{narray_id}}}, {{{key_cast}}}e.getKey(), {{{value_cast}}}e.getValue());"""
-                       else if java_type.is_iterable then
-                               loop_header = """for ({{{java_type.generic_params[0]}}} e: {{{jarray_id}}})"""
-                               loop_body   = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}e);"""
-                       else
-                               loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
-                               loop_body   = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}{{{jarray_id}}}[i]);"""
-                       end
+                       name = self
                end
 
-               return loop_header + "\n" + loop_body
+               return name.to_snake_case
        end
 end
index 1ede6f0..9ee4190 100644 (file)
@@ -21,7 +21,7 @@ module javap_visitor
 import javap_test_parser
 import code_generator
 import jtype_converter
-intrude import types
+intrude import model
 
 class JavaVisitor
        super Visitor
@@ -336,11 +336,11 @@ redef class Nimplements_declaration
        end
 end
 
-#                                          #
-#   F I E L D    D E C L A R A T I O N S   #
-#                                          #
+#                                            #
+# P R O P E R T Y    D E C L A R A T I O N S #
+#                                            #
 
-# Method declaration in the field declarations
+# Method declaration
 redef class Nmethod_declaration
        redef fun accept_visitor(v)
        do
@@ -357,7 +357,7 @@ redef class Nmethod_declaration
        end
 end
 
-# Constructor declaration in the field declarations
+# Constructor declaration
 redef class Nconstructor_declaration
        redef fun accept_visitor(v)
        do
@@ -367,7 +367,7 @@ redef class Nconstructor_declaration
        end
 end
 
-# Variable declaration in the field declarations
+# Variable property declaration
 redef class Nvariable_declaration
        redef fun accept_visitor(v)
        do
@@ -382,7 +382,7 @@ redef class Nvariable_declaration
        end
 end
 
-# Static declaration in the field declarations
+# Static property declaration
 redef class Nstatic_declaration
        redef fun accept_visitor(v)
        do
@@ -392,7 +392,7 @@ redef class Nstatic_declaration
        end
 end
 
-# Identifier of the field
+# Identifier of a variable
 redef class Nvariable_id
        redef fun accept_visitor(v)
        do
similarity index 92%
rename from contrib/jwrapper/src/types.nit
rename to contrib/jwrapper/src/model.nit
index 4fb5251..b77c734 100644 (file)
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Contains the java and nit type representation used to convert java to nit code
-module types
+module model
 
 import jtype_converter
 
@@ -67,7 +67,7 @@ class JavaType
                end
 
                if not self.has_generic_params then return nit_type
-               
+
                nit_type.generic_params = new Array[NitType]
 
                for param in generic_params do
@@ -123,7 +123,7 @@ class JavaType
                        for i in [0..array_dimension[ do
                                id += "[]"
                        end
-               else if self.has_generic_params then 
+               else if self.has_generic_params then
                        var gen_list = new Array[String]
 
                        for param in generic_params do
@@ -277,7 +277,7 @@ class NitType
        do
                var id = self.identifier
 
-               if self.has_generic_params then 
+               if self.has_generic_params then
                        var gen_list = new Array[String]
 
                        for param in generic_params do
@@ -294,36 +294,36 @@ end
 class JavaClass
        var class_type = new JavaType(new JavaTypeConverter)
        var attributes = new HashMap[String, JavaType]
-       var methods = new HashMap[String, Array[JReturnAndParams]]
+
+       # Methods of this class organized by their name
+       var methods = new HashMap[String, Array[JavaMethod]]
+
        var unknown_types = new HashSet[JavaType]
        var imports = new HashSet[NitModule]
 
        fun add_method(id: String, return_type: JavaType, params: Array[JavaType])
        do
-               var ret_and_params = methods.get_or_default(id, new Array[JReturnAndParams])
-               
-               ret_and_params.add(new JReturnAndParams(return_type, new Array[JavaType].from(params)))
-               methods[id] = ret_and_params
+               var signatures = methods.get_or_default(id, new Array[JavaMethod])
+               signatures.add(new JavaMethod(return_type, new Array[JavaType].from(params)))
+               methods[id] = signatures
        end
 end
 
-class JReturnAndParams
+# A Java method, with its signature
+class JavaMethod
+       # Type returned by the method
        var return_type: JavaType
-       var params: Array[JavaType]
 
-       init(return_type: JavaType, params: Array[JavaType])
-       do
-               self.return_type = return_type
-               self.params = params
-       end
+       # Type of the arguments of the method
+       var params: Array[JavaType]
 end
 
+# A Nit module, use to import the referenced extern classes
 class NitModule
-       var value: String
-
-       init(str: String) do value = str
+       # Name of the module
+       var name: String
 
        redef fun ==(other): Bool do return self.to_s == other.to_s
-       redef fun to_s: String do return self.value
-       redef fun hash: Int do return self.value.hash
+       redef fun to_s: String do return self.name
+       redef fun hash: Int do return self.name.hash
 end
index d5db709..3dec44f 100644 (file)
@@ -54,6 +54,7 @@ class Nitiwiki
                sys.system "rsync -vr --delete {root}/ {config.rsync_dir}"
        end
 
+       # Pull data from git repository.
        fun fetch do
                sys.system "git pull {config.git_origin} {config.git_branch}"
        end
@@ -211,6 +212,9 @@ class Nitiwiki
                return path.simplify_path
        end
 
+       # Transform an id style name into a pretty printed name.
+       #
+       # Used to translate ids in beautiful page names.
        fun pretty_name(name: String): String do
                name = name.replace("_", " ")
                name = name.capitalized
index 266e1d9..7f213b7 100644 (file)
@@ -110,7 +110,7 @@ redef class WikiSection
                for child in children.values do
                        if child isa WikiArticle and child.is_index then return child
                end
-               return new WikiSectionIndex(wiki, self)
+               return new WikiSectionIndex(wiki, "index", self)
        end
 
        redef fun tpl_link do return index.tpl_link
@@ -322,11 +322,6 @@ class WikiSectionIndex
        # The section described by `self`.
        var section: WikiSection
 
-       init(wiki: Nitiwiki, section: WikiSection) do
-               super(wiki, "index")
-               self.section = section
-       end
-
        redef var is_dirty = false
 
        redef fun tpl_article do
@@ -341,11 +336,19 @@ end
 class TplArticle
        super Template
 
+       # Article title.
        var title: nullable Streamable = null
+
+       # Article HTML body.
        var body: nullable Streamable = null
+
+       # Sidebar of this article (if any).
        var sidebar: nullable TplSidebar = null
+
+       # Breadcrumbs from wiki root to this article.
        var breadcrumbs: nullable TplBreadcrumbs = null
 
+       # Init `self` with a `title`.
        init with_title(title: Streamable) do
                self.title = title
        end
index 18ee374..dacc9ff 100644 (file)
@@ -46,7 +46,7 @@ in "C++" `{
 
 redef class AnalysisManager
 
-       fun run(src: String)
+       fun run_web(src: String)
        do
                sys.suggest_garbage_collection
 
@@ -109,7 +109,7 @@ redef class AnalysisManager
 end
 
 redef class NativeString
-       fun run_analysis do manager.run to_s
+       fun run_analysis do manager.run_web to_s
 end
 
 fun dummy_set_callbacks import NativeString.run_analysis in "C++" `{
diff --git a/lib/android/README.md b/lib/android/README.md
new file mode 100644 (file)
index 0000000..1f4e659
--- /dev/null
@@ -0,0 +1,108 @@
+The `android` module provides support for the Android platform
+
+# Compilation for Android
+
+The compiler generates an APK file as the output when the `android`
+module is imported by the compilation target. The path to the generated
+file can be specified using the `-o` and `--dir` options.
+
+# Host system configuration
+
+To compile for Android, you must install the Android SDK and NDK.
+The tools `android`, `ndk-build` and `ant` must be in your PATH.
+
+# Configure your Android application
+
+The `app.nit` framework and this project offers some services to
+customized the generated Android application.
+
+## Module annotations
+
+* `app_version` specifies the version of the generated APK file.
+It takes 3 arguments: the major, minor and revision version numbers.
+The special function `git_revision` will use the prefix of the hash of the
+latest git commit. The default version is 1.0.
+
+    Example: `app_version(1, 0, git_revision)`
+
+* `app_name` takes a single argument, the visible name of the Android
+application. By default, the compiler would use the name of the target
+module. This name will be used as the name of the main activity and
+as the launcher name.
+
+    Example: `app_name "My App"`
+
+* `java_package` specifies the package used by the generated Java
+classes and the APK file. Once the application is published, this
+value should not be changed. By default, the compiler will use
+the package `org.nitlanguage.{module_name}`.
+
+* Custom information can be added to the Android manifest file
+using the annotations `android_manifest`, `android_manifest_application`
+and `android_manifest_activity`.
+
+    Example usage to specify an extra permission:
+
+    ~~~
+    android_manifest """<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>"""
+    ~~~
+
+* The API version target can be specified with `min_api_version`,
+`max_api_version` and `target_api_version`. These take a single
+integer as argument. They are applied in the Android manifest as
+`minSdkVesion`, `targetSdkVersion` and `maxSdkVersion`.
+
+    See http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
+
+## Project entry points
+
+Importing `android::landscape` or `android::portrait` locks the generated
+application in the specified orientation. This can be useful for games and
+other multimedia applications.
+
+# Compilation modes
+
+There are two compilation modes for the Android platform, debug and release.
+Theses modes are also applied to the generated Android projects.
+The compilation mode is specified as an argument to `nitc`, only
+`--release` can be specified as debug is the default behavior.
+
+## Debug mode
+
+Debug mode enables compiling to an APK file without handling signing keys
+and their password. The APK file can be installed to a local device with
+USB debugging enabled, but it cannot be published on the Play Store.
+
+By default, `nitc` will compile Android applications in debug mode.
+
+## Release mode
+
+Building in release mode will use your private key to sign the
+APK file, it can then be published on the Play Store.
+
+1. Have a keystore with a valid key to sign your APK file.
+
+    To create a new keystore, avoid using the default values of `jarsigner`
+as they change between versions of the Java SDK. You should instead use a
+command similar to the following, replacing `KEYSTORE_PATH` and `KEY_ALIAS`
+with the desired values.
+
+    ~~~
+    keytool -genkey -keystore KEYSTORE_PATH -alias KEY_ALIAS -sigalg MD5withRSA -keyalg RSA -keysize 1024 -validity 10000
+    ~~~
+
+2. Set the environment variables used by `nitc`: `KEYSTORE`, `KEY_ALIAS` and
+optionally `TSA_SERVER`. These settings can be set in a startup script such as
+`~/.bashrc` or in a local Makefile.
+
+    You can use the following commands by replacing the right hand values
+to your own configuration.
+
+    ~~~
+    export KEYSTORE=keystore_path
+    export KEY_ALIAS=key_alias
+    export TSA_SERVER=timestamp_authority_server_url # Optional
+    ~~~
+
+3. Call `nitc` with the `--release` options. You will be prompted for the
+required passwords as needed by `jarsigner`.
index 5523f30..584b89b 100644 (file)
 
 # Android services and implementation of app.nit
 #
-# To use this module and compile for Android, you must install the
-# Android SDK (with API level 10) and NDK (with the API level 9).
-# The tools `android`, `ndk-build` and `ant` must be in your PATH.
-#
 # This module provides basic logging facilities, advanced logging can be
 # achieved by importing `android::log`.
 module android
index f112d7a..4d2edb4 100644 (file)
@@ -21,6 +21,7 @@ redef class Sys
        # Keeps the average length of an Array when calling to_s
        var arr_len = new Counter[Int]
 
+       # Compute the average array length.
        fun avg_arr_len: Float do
                var total = 0
                var sum = 0
@@ -31,6 +32,7 @@ redef class Sys
                return sum.to_f / total.to_f
        end
 
+       # Compute the average string length.
        fun avg_s_len: Float do
                var total = 0
                var sum = 0
@@ -41,6 +43,7 @@ redef class Sys
                return sum.to_f / total.to_f
        end
 
+       # Display statistics in standard output.
        fun print_stats do
                if arr_len.sum == 0 then
                        print "*** No Array stats ***"
index 703ff5c..ae0d792 100644 (file)
@@ -179,8 +179,6 @@ class Game
        # but cannot be used to add new Events.
        var last_turn: nullable ThinGameTurn[G] = null
 
-       init do end
-
        # Execute and return a new GameTurn.
        #
        # This method calls `do_pre_turn` before executing the GameTurn
index bca105c..c087642 100644 (file)
@@ -85,8 +85,7 @@ class CurlHTTPRequest
                curl.prim_curl.easy_setopt(new CURLOption.user_agent, name)
        end
 
-       init (url: String, curl: nullable Curl)
-       do
+       init (url: String, curl: nullable Curl) is old_style_init do
                self.url = url
                self.curl = curl
        end
@@ -211,8 +210,7 @@ class CurlMailRequest
        var body: nullable String = "" is writable
        private var supported_outgoing_protocol: Array[String] = ["smtp", "smtps"]
 
-       init (curl: nullable Curl)
-       do
+       init (curl: nullable Curl) is old_style_init do
                self.curl = curl
        end
 
@@ -351,12 +349,6 @@ class CurlResponseFailed
 
        var error_code: Int
        var error_msg: String
-
-       init (err_code: Int, err_msg: String)
-       do
-               self.error_code = err_code
-               self.error_msg = err_msg
-       end
 end
 
 # Success Abstract Response Success Class
@@ -467,7 +459,7 @@ class HeaderMapIterator
        super MapIterator[String, String]
 
        private var iterator: Iterator[Couple[String, String]]
-       init(map: HeaderMap) do self.iterator = map.arr.iterator
+       init(map: HeaderMap) is old_style_init do self.iterator = map.arr.iterator
 
        redef fun is_ok do return self.iterator.is_ok
        redef fun next do self.iterator.next
index 2797b67..8fb57fd 100644 (file)
@@ -5,7 +5,7 @@
 #
 # This file is free software, which comes along with NIT.  This software is
 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A 
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
 # PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
 # is kept unaltered, and a notification of the changes is added.
 # You  are  allowed  to  redistribute it and sell it, alone or is a part of
@@ -83,8 +83,8 @@ class StreamCat
        do
                _streams = streams.iterator
        end
-       init(streams: IStream ...)
-       do
+
+       init(streams: IStream ...) is old_style_init do
                _streams = streams.iterator
        end
 end
@@ -133,8 +133,7 @@ class StreamDemux
                _streams = streams
        end
 
-       init(streams: OStream ...)
-       do
+       init(streams: OStream ...) is old_style_init do
                _streams = streams
        end
 end
index 9172e19..9a9b4b1 100644 (file)
@@ -202,6 +202,71 @@ class GithubAPI
                if was_error then return null
                return commit
        end
+
+       # Get the Github issue #`number`.
+       #
+       # Returns `null` if the issue cannot be found.
+       #
+       #     var api = new GithubAPI(get_github_oauth)
+       #     var repo = api.load_repo("privat/nit")
+       #     assert repo != null
+       #     var issue = api.load_issue(repo, 1)
+       #     assert issue.title == "Doc"
+       fun load_issue(repo: Repo, number: Int): nullable Issue do
+               var issue = new Issue(self, repo, number)
+               issue.load_from_github
+               if was_error then return null
+               return issue
+       end
+
+       # Get the Github pull request #`number`.
+       #
+       # Returns `null` if the pull request cannot be found.
+       #
+       #     var api = new GithubAPI(get_github_oauth)
+       #     var repo = api.load_repo("privat/nit")
+       #     assert repo != null
+       #     var pull = api.load_pull(repo, 1)
+       #     assert pull.title == "Doc"
+       #     assert pull.user.login == "Morriar"
+       fun load_pull(repo: Repo, number: Int): nullable PullRequest do
+               var pull = new PullRequest(self, repo, number)
+               pull.load_from_github
+               if was_error then return null
+               return pull
+       end
+
+       # Get the Github label with `name`.
+       #
+       # Returns `null` if the label cannot be found.
+       #
+       #     var api = new GithubAPI(get_github_oauth)
+       #     var repo = api.load_repo("privat/nit")
+       #     assert repo isa Repo
+       #     var labl = api.load_label(repo, "ok_will_merge")
+       #     assert labl != null
+       fun load_label(repo: Repo, name: String): nullable Label do
+               var labl = new Label(self, repo, name)
+               labl.load_from_github
+               if was_error then return null
+               return labl
+       end
+
+       # Get the Github milestone with `id`.
+       #
+       # Returns `null` if the milestone cannot be found.
+       #
+       #     var api = new GithubAPI(get_github_oauth)
+       #     var repo = api.load_repo("privat/nit")
+       #     assert repo isa Repo
+       #     var stone = api.load_milestone(repo, 4)
+       #     assert stone.title == "v1.0prealpha"
+       fun load_milestone(repo: Repo, id: Int): nullable Milestone do
+               var milestone = new Milestone(self, repo, id)
+               milestone.load_from_github
+               if was_error then return null
+               return milestone
+       end
 end
 
 # Something returned by the Github API.
@@ -300,6 +365,82 @@ class Repo
                return res
        end
 
+       # List of issues associated with their ids.
+       fun issues: Map[Int, Issue] do
+               api.message(1, "Get issues for {full_name}")
+               var res = new HashMap[Int, Issue]
+               var issue = last_issue
+               if issue == null then return res
+               res[issue.number] = issue
+               while issue.number > 1 do
+                       issue = api.load_issue(self, issue.number - 1)
+                       assert issue isa Issue
+                       res[issue.number] = issue
+               end
+               return res
+       end
+
+       # Get the last published issue.
+       fun last_issue: nullable Issue do
+               var array = api.get("repos/{full_name}/issues")
+               if not array isa JsonArray then return null
+               if array.is_empty then return null
+               var obj = array.first
+               if not obj isa JsonObject then return null
+               return new Issue.from_json(api, self, obj)
+       end
+
+       # List of labels associated with their names.
+       fun labels: Map[String, Label] do
+               api.message(1, "Get labels for {full_name}")
+               var array = api.get("repos/{full_name}/labels")
+               var res = new HashMap[String, Label]
+               if not array isa JsonArray then return res
+               for obj in array do
+                       if not obj isa JsonObject then continue
+                       var name = obj["name"].to_s
+                       res[name] = new Label.from_json(api, self, obj)
+               end
+               return res
+       end
+
+       # List of milestones associated with their ids.
+       fun milestones: Map[Int, Milestone] do
+               api.message(1, "Get milestones for {full_name}")
+               var array = api.get("repos/{full_name}/milestones")
+               var res = new HashMap[Int, Milestone]
+               if array isa JsonArray then
+                       for obj in array do
+                               if not obj isa JsonObject then continue
+                               var number = obj["number"].as(Int)
+                               res[number] = new Milestone.from_json(api, self, obj)
+                       end
+               end
+               return res
+       end
+
+       # List of pull-requests associated with their ids.
+       #
+       # Implementation notes: because PR numbers are not consecutive,
+       # PR are loaded from pages.
+       # See: https://developer.github.com/v3/pulls/#list-pull-requests
+       fun pulls: Map[Int, PullRequest] do
+               api.message(1, "Get pulls for {full_name}")
+               var res = new HashMap[Int, PullRequest]
+               var page = 1
+               var array = api.get("{key}/pulls?page={page}").as(JsonArray)
+               while not array.is_empty do
+                       for obj in array do
+                               if not obj isa JsonObject then continue
+                               var number = obj["number"].as(Int)
+                               res[number] = new PullRequest.from_json(api, self, obj)
+                       end
+                       page += 1
+                       array = api.get("{key}/pulls?page={page}").as(JsonArray)
+               end
+               return res
+       end
+
        # Repo default branch.
        fun default_branch: Branch do
                var name = json["default_branch"].to_s
@@ -432,3 +573,278 @@ class Commit
        # Commit message.
        fun message: String do return json["commit"].as(JsonObject)["message"].to_s
 end
+
+# A Github issue.
+#
+# Should be accessed from `GithubAPI::load_issue`.
+#
+# See <https://developer.github.com/v3/issues/>.
+class Issue
+       super RepoEntity
+
+       redef var key is lazy do return "{repo.key}/issues/{number}"
+
+       # Issue Github ID.
+       var number: Int
+
+       redef init from_json(api, repo, json) do
+               self.number = json["number"].as(Int)
+               super
+       end
+
+       # Issue title.
+       fun title: String do return json["title"].to_s
+
+       # User that created this issue.
+       fun user: User do
+               return new User.from_json(api, json["user"].as(JsonObject))
+       end
+
+       # List of labels on this issue associated to their names.
+       fun labels: Map[String, Label] do
+               var res = new HashMap[String, Label]
+               for obj in json["labels"].as(JsonArray) do
+                       if not obj isa JsonObject then continue
+                       var name = obj["name"].to_s
+                       res[name] = new Label.from_json(api, repo, obj)
+               end
+               return res
+       end
+
+       # State of the issue on Github.
+       fun state: String do return json["state"].to_s
+
+       # Is the issue locked?
+       fun locked: Bool do return json["locked"].as(Bool)
+
+       # Assigned `User` (if any).
+       fun assignee: nullable User do
+               var assignee = json["assignee"]
+               if not assignee isa JsonObject then return null
+               return new User.from_json(api, assignee)
+       end
+
+       # `Milestone` (if any).
+       fun milestone: nullable Milestone do
+               var milestone = json["milestone"]
+               if not milestone isa JsonObject then return null
+               return new Milestone.from_json(api, repo, milestone)
+       end
+
+       # Number of comments on this issue.
+       fun comments_count: Int do return json["comments"].to_s.to_i
+
+       # Creation time in ISODate format.
+       fun created_at: ISODate do
+               return new ISODate.from_string(json["created_at"].to_s)
+       end
+
+       # Last update time in ISODate format (if any).
+       fun updated_at: nullable ISODate do
+               var res = json["updated_at"]
+               if res == null then return null
+               return new ISODate.from_string(res.to_s)
+       end
+
+       # Close time in ISODate format (if any).
+       fun closed_at: nullable ISODate do
+               var res = json["closed_at"]
+               if res == null then return null
+               return new ISODate.from_string(res.to_s)
+       end
+
+       # TODO link to pull request
+
+       # Full description of the issue.
+       fun body: String  do return json["body"].to_s
+
+       # User that closed this issue (if any).
+       fun closed_by: nullable User do
+               var closer = json["closed_by"]
+               if not closer isa JsonObject then return null
+               return new User.from_json(api, closer)
+       end
+end
+
+# A Github pull request.
+#
+# Should be accessed from `GithubAPI::load_pull`.
+#
+# PullRequest are basically Issues with more data.
+# See <https://developer.github.com/v3/pulls/>.
+class PullRequest
+       super Issue
+
+       redef var key is lazy do return "{repo.key}/pulls/{number}"
+
+       # Merge time in ISODate format (if any).
+       fun merged_at: nullable ISODate do
+               var res = json["merged_at"]
+               if res == null then return null
+               return new ISODate.from_string(res.to_s)
+       end
+
+       # Merge commit SHA.
+       fun merge_commit_sha: String do return json["merge_commit_sha"].to_s
+
+       # Count of comments made on the pull request diff.
+       fun review_comments: Int do return json["review_comments"].to_s.to_i
+
+       # Pull request head (can be a commit SHA or a branch name).
+       fun head: PullRef do
+               var json = json["head"].as(JsonObject)
+               return new PullRef(api, json)
+       end
+
+       # Pull request base (can be a commit SHA or a branch name).
+       fun base: PullRef do
+               var json = json["base"].as(JsonObject)
+               return new PullRef(api, json)
+       end
+
+       # Is this pull request merged?
+       fun merged: Bool do return json["merged"].as(Bool)
+
+       # Is this pull request mergeable?
+       fun mergeable: Bool do return json["mergeable"].as(Bool)
+
+       # Mergeable state of this pull request.
+       #
+       # See <https://developer.github.com/v3/pulls/#list-pull-requests>.
+       fun mergeable_state: Int do return json["mergeable_state"].to_s.to_i
+
+       # User that merged this pull request (if any).
+       fun merged_by: nullable User do
+               var merger = json["merged_by"]
+               if not merger isa JsonObject then return null
+               return new User.from_json(api, merger)
+       end
+
+       # Count of commits in this pull request.
+       fun commits: Int do return json["commits"].to_s.to_i
+
+       # Added line count.
+       fun additions: Int do return json["additions"].to_s.to_i
+
+       # Deleted line count.
+       fun deletions: Int do return json["deletions"].to_s.to_i
+
+       # Changed files count.
+       fun changed_files: Int do return json["changed_files"].to_s.to_i
+end
+
+# A pull request reference (used for head and base).
+class PullRef
+
+       # Api instance that maintains self.
+       var api: GithubAPI
+
+       # JSON representation.
+       var json: JsonObject
+
+       # Label pointed by `self`.
+       fun labl: String do return json["label"].to_s
+
+       # Reference pointed by `self`.
+       fun ref: String do return json["ref"].to_s
+
+       # Commit SHA pointed by `self`.
+       fun sha: String do return json["sha"].to_s
+
+       # User pointed by `self`.
+       fun user: User do
+               return new User.from_json(api, json["user"].as(JsonObject))
+       end
+
+       # Repo pointed by `self`.
+       fun repo: Repo do
+               return new Repo.from_json(api, json["repo"].as(JsonObject))
+       end
+end
+
+# A Github label.
+#
+# Should be accessed from `GithubAPI::load_label`.
+#
+# See <https://developer.github.com/v3/issues/labels/>.
+class Label
+       super RepoEntity
+
+       redef var key is lazy do return "{repo.key}/labels/{name}"
+
+       # Label name.
+       var name: String
+
+       redef init from_json(api, repo, json) do
+               self.name = json["name"].to_s
+               super
+       end
+
+       # Label color code.
+       fun color: String do return json["color"].to_s
+end
+
+# A Github milestone.
+#
+# Should be accessed from `GithubAPI::load_milestone`.
+#
+# See <https://developer.github.com/v3/issues/milestones/>.
+class Milestone
+       super RepoEntity
+
+       redef var key is lazy do return "{repo.key}/milestones/{number}"
+
+       # The milestone id on Github.
+       var number: Int
+
+       redef init from_json(api, repo, json) do
+               super
+               self.number = json["number"].as(Int)
+       end
+
+       # Milestone title.
+       fun title: String do return json["title"].to_s
+
+       # Milestone long description.
+       fun description: String do return json["description"].to_s
+
+       # Count of opened issues linked to this milestone.
+       fun open_issues: Int do return json["open_issues"].to_s.to_i
+
+       # Count of closed issues linked to this milestone.
+       fun closed_issues: Int do return json["closed_issues"].to_s.to_i
+
+       # Milestone state.
+       fun state: String do return json["state"].to_s
+
+       # Creation time in ISODate format.
+       fun created_at: ISODate do
+               return new ISODate.from_string(json["created_at"].to_s)
+       end
+
+       # User that created this milestone.
+       fun creator: User do
+               return new User.from_json(api, json["creator"].as(JsonObject))
+       end
+
+       # Due time in ISODate format (if any).
+       fun due_on: nullable ISODate do
+               var res = json["updated_at"]
+               if res == null then return null
+               return new ISODate.from_string(res.to_s)
+       end
+
+       # Update time in ISODate format (if any).
+       fun updated_at: nullable ISODate do
+               var res = json["updated_at"]
+               if res == null then return null
+               return new ISODate.from_string(res.to_s)
+       end
+
+       # Close time in ISODate format (if any).
+       fun closed_at: nullable ISODate do
+               var res = json["closed_at"]
+               if res == null then return null
+               return new ISODate.from_string(res.to_s)
+       end
+end
index 60d2f12..71868b6 100644 (file)
@@ -37,10 +37,7 @@ class ConfigTree
        # The ini file used to read/store data
        var ini_file: String
 
-       init(file: String) do
-               self.ini_file = file
-               if file.file_exists then load
-       end
+       init do if ini_file.file_exists then load
 
        # Get the config value for `key`
        #
@@ -103,7 +100,6 @@ class ConfigTree
        #    assert config.has_key("foo.bar")
        #    assert not config.has_key("zoo")
        fun has_key(key: String): Bool do
-               var children = roots
                var parts = key.split(".").reversed
                var node = get_root(parts.pop)
                if node == null then return false
@@ -223,7 +219,6 @@ class ConfigTree
        private var roots = new Array[ConfigNode]
 
        private fun set_node(key: String, value: nullable String) do
-               var children = roots
                var parts = key.split(".").reversed
                var k = parts.pop
                var root = get_root(k)
@@ -250,7 +245,6 @@ class ConfigTree
        end
 
        private fun get_node(key: String): nullable ConfigNode do
-               var children = roots
                var parts = key.split(".").reversed
                var node = get_root(parts.pop)
                while not parts.is_empty do
@@ -283,14 +277,11 @@ class ConfigTree
 end
 
 private class ConfigNode
-       var parent: nullable ConfigNode
+
+       var parent: nullable ConfigNode = null
        var children = new HashMap[String, ConfigNode]
        var name: String is writable
-       var value: nullable String
-
-       init(name: String) do
-               self.name = name
-       end
+       var value: nullable String = null
 
        fun key: String do
                if parent == null then
index bb578fd..12dbc7c 100644 (file)
@@ -34,12 +34,28 @@ interface Jsonable
        # SEE: `append_json`
        fun to_json: String is abstract
 
+       # Use `append_json` to implement `to_json`.
+       #
+       # Therefore, one that redefine `append_json` may use the following
+       # redefinition to link `to_json` and `append_json`:
+       #
+       # ~~~nitish
+       # redef fun to_json do return to_json_by_append
+       # ~~~
+       #
+       # Note: This is not the default implementation of `to_json` in order to
+       # avoid cyclic references between `append_json` and `to_json` when none are
+       # implemented.
+       protected fun to_json_by_append: String do
+               var buffer = new RopeBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
        # Append the JSON representation of `self` to the specified buffer.
        #
        # SEE: `to_json`
-       fun append_json(buffer: Buffer) do
-               buffer.append(to_json)
-       end
+       fun append_json(buffer: Buffer) do buffer.append(to_json)
 end
 
 redef class Text
@@ -82,11 +98,7 @@ redef class Text
        #
        #     assert "\t\"http://example.com\"\r\n\0\\".to_json ==
        #               "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
-       redef fun to_json do
-               var buffer = new FlatBuffer
-               append_json(buffer)
-               return buffer.write_to_string
-       end
+       redef fun to_json do return to_json_by_append
 
        # Parse `self` as JSON.
        #
@@ -211,11 +223,7 @@ interface JsonMapRead[K: String, V: nullable Jsonable]
        #     obj = new JsonObject
        #     obj["baz"] = null
        #     assert obj.to_json == "\{\"baz\":null\}"
-       redef fun to_json do
-               var buffer = new FlatBuffer
-               append_json(buffer)
-               return buffer.write_to_string
-       end
+       redef fun to_json do return to_json_by_append
 
        private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
                        buffer: Buffer) do
@@ -259,11 +267,7 @@ class JsonSequenceRead[E: nullable Jsonable]
        #     assert arr.to_json =="[\"foo\"]"
        #     arr.pop
        #     assert arr.to_json =="[]"
-       redef fun to_json do
-               var buffer = new FlatBuffer
-               append_json(buffer)
-               return buffer.write_to_string
-       end
+       redef fun to_json do return to_json_by_append
 
        private fun append_json_entry(iterator: Iterator[nullable Jsonable],
                        buffer: Buffer) do
diff --git a/lib/neo4j/graph/graph.nit b/lib/neo4j/graph/graph.nit
new file mode 100644 (file)
index 0000000..39063f2
--- /dev/null
@@ -0,0 +1,278 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Provides an interface for services on a Neo4j graphs.
+module neo4j::graph::graph
+
+import neo4j
+import progression
+
+# A Neo4j graph with a local identification scheme for its nodes.
+#
+# An identification scheme can be defined by subclassing `NeoNodeCollection`.
+#
+# `GraphStore` can be subclassed to add ways to save or load a graph. The
+# storing mechanisms may use `nodes.id_of` to identify the nodes in the graph
+# while encoding the relationships.
+class NeoGraph
+       # All the nodes in the graph.
+       var nodes: NeoNodeCollection
+
+       # All the relationships in the graph.
+       var edges: SimpleCollection[NeoEdge] = new Array[NeoEdge]
+
+       # Add a new node to the graph and return it.
+       #
+       # Set the local ID of the node before returning it.
+       #
+       # SEE: `NeoNodeCollection.add`
+       # SEE: `NeoNodeCollection.create_node`
+       # SEE: `NeoNodeCollection.register`
+       fun create_node: NeoNode do return nodes.create_node
+end
+
+# All the nodes in a `NeoGraph`.
+#
+# An identification scheme can be defined throught the `register` and `add`
+# methods. The `id_property` attribute defines where the local ID (that is the
+# ID managed by the collection) is stored in each node.
+abstract class NeoNodeCollection
+       super SimpleCollection[NeoNode]
+
+       # The type of the local IDs.
+       type ID_TYPE: Jsonable
+
+       # The property of the nodes that hold the local ID.
+       var id_property: String
+
+       # Retrieve the node that has the specified local id.
+       #
+       # Note: The default implementation uses `get_or_null`.
+       fun [](id: ID_TYPE): NeoNode do
+               var n = get_or_null(id)
+               assert n isa NeoNode
+               return n
+       end
+
+       # Retrieve the node that has the specified local id, or return `null`.
+       #
+       # Note: The default implementation uses `iterator`.
+       fun get_or_null(id: ID_TYPE): nullable NeoNode do
+               for n in self do
+                       if id_of(n) == id then return n
+               end
+               return null
+       end
+
+       # There is a node that has the specified local id?
+       #
+       # Note: The default implementation uses `get_or_null`.
+       fun has_id(id: ID_TYPE): Bool do return get_or_null(id) isa NeoNode
+
+       # Return the local ID of the node.
+       fun id_of(node: NeoNode): ID_TYPE do return node[id_property].as(ID_TYPE)
+
+       # Set the local ID of the specified node.
+       #
+       # Just update the property at `property_id`. Do not check anything.
+       protected fun id_of=(node: NeoNode, id: ID_TYPE) do
+               node[id_property] = id
+       end
+
+       # Enlarge the collection to have at least the specified capacity.
+       #
+       # The capacity is specified in number of nodes. Used to minimize the
+       # number of times the collection need to be resized when adding nodes
+       # in batches.
+       #
+       # Do nothing by default.
+       fun enlarge(cap: Int) do end
+
+       # Add the specified node to the graph and set its local ID.
+       #
+       # SEE: `add`
+       # SEE: `create_node`
+       fun register(node: NeoNode) is abstract
+
+       # Add the specified node to the graph assuming that its local ID is already set.
+       #
+       # SEE: `create_node`
+       # SEE: `register`
+       redef fun add(node: NeoNode) is abstract
+
+       # Add a new node to the graph and return it.
+       #
+       # Set the local ID of the node before returning it.
+       #
+       # SEE: `add`
+       # SEE: `register`
+       fun create_node: NeoNode do
+               var node = new NeoNode
+               register(node)
+               return node
+       end
+
+       # Remove the node with the specified local ID.
+       fun remove_at(id: ID_TYPE) is abstract
+
+       # Remove the specified node.
+       #
+       # The local ID is used instead of `==` to seek the node.
+       fun remove_node(node: NeoNode) do
+               remove_at(id_of(node))
+       end
+
+       redef fun clear do
+               for node in self do remove_node(node)
+       end
+
+       redef fun remove(node: NeoNode) do
+               for n in self do
+                       if node == n then
+                               remove_node(n)
+                               return
+                       end
+               end
+       end
+
+       redef fun remove_all(node: NeoNode) do
+               for n in self do
+                       if node == n then remove_node(n)
+               end
+       end
+
+       # Optimize the collection, possibly by rewritting it.
+       #
+       # The local ID of the elements may be changed by this method.
+       fun compact do end
+end
+
+# A mean to save and load a Neo4j graph.
+abstract class GraphStore
+       super Trackable
+
+       # The graph to save or load.
+       var graph: NeoGraph
+
+       # Can we save the graph without conflict?
+       fun isolated_save: Bool is abstract
+
+       # Load the graph (or a part of it).
+       #
+       # Do not reset the graph.
+       fun load is abstract
+
+       # Save the graph.
+       fun save do save_part(graph.nodes, graph.edges)
+
+       # Save the specified part of the graph.
+       #
+       # Assume that for each relationship specified, both ends are already saved
+       # or are specified in the same call to this method.
+       fun save_part(nodes: Collection[NeoNode],
+                       edges: Collection[NeoEdge]) is abstract
+end
+
+# Save or load a graph using an actual Neo4j database.
+class Neo4jGraphStore
+       super GraphStore
+
+       # The maximum number of entities saved in one request.
+       #
+       # Also defines the granulity of the reported progression.
+       #
+       # TODO Also honor this limit in `load`.
+       var batch_max_size = 512 is writable
+
+       # The Neo4j client to use.
+       var client: Neo4jClient
+
+       # The label to use to retrieve the nodes.
+       var node_label: String
+
+       private var done_part = 0
+       private var total = 0
+
+       # Is the database already contains at least one node with the specified label?
+       fun has_node_label(name: String): Bool do
+               var query = new CypherQuery.from_string(
+                               "match n where \{name\} in labels(n) return count(n)")
+               query.params["name"] = name
+               var data = client.cypher(query).as(JsonObject)["data"]
+               var result = data.as(JsonArray).first.as(JsonArray).first.as(Int)
+               return result > 0
+       end
+
+       redef fun isolated_save do return not has_node_label(node_label)
+
+       redef fun load do
+               assert batch_max_size > 0
+               fire_started
+               var db_nodes = client.nodes_with_label(node_label)
+               var nodes = graph.nodes
+               var edges = graph.edges
+               var i = 0
+
+               total = nodes.length * 2
+               done_part = nodes.length
+               fire_progressed(done_part, total)
+               for node in db_nodes do
+                       nodes.add(node)
+                       edges.add_all(node.out_edges)
+                       i += 1
+                       if i >= batch_max_size then
+                               done_part += batch_max_size
+                               fire_progressed(done_part, total)
+                       end
+               end
+               fire_done
+       end
+
+       redef fun save_part(nodes, edges) do
+               assert batch_max_size > 0
+               fire_started
+               total = nodes.length + edges.length
+               done_part = 0
+
+               save_entities(nodes)
+               save_entities(edges)
+               fire_done
+       end
+
+       # Save the specified entities.
+       private fun save_entities(neo_entities: Collection[NeoEntity]) do
+               var batch = new NeoBatch(client)
+               var batch_length = 0
+
+               for nentity in neo_entities do
+                       batch.save_entity(nentity)
+                       batch_length += 1
+                       if batch_length >= batch_max_size then
+                               do_batch(batch)
+                               done_part += batch_max_size
+                               fire_progressed(done_part, total)
+                               batch = new NeoBatch(client)
+                               batch_length = 0
+                       end
+               end
+               do_batch(batch)
+               done_part += batch_length
+       end
+
+       # Execute `batch` and check for errors.
+       #
+       # Abort if `batch.execute` returns errors.
+       private fun do_batch(batch: NeoBatch) do
+               var errors = batch.execute
+               assert errors.is_empty else
+                       for e in errors do sys.stderr.write("{e}\n")
+               end
+       end
+end
diff --git a/lib/neo4j/graph/json_graph_store.nit b/lib/neo4j/graph/json_graph_store.nit
new file mode 100644 (file)
index 0000000..9c97216
--- /dev/null
@@ -0,0 +1,321 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Provides JSON as a mean to store graphs.
+module neo4j::graph::json_graph_store
+
+import graph
+
+# Save or load a graph using a JSON document.
+#
+# The graph (or the specified part of it) is stored as a JSON object with the
+# following properties:
+#
+# * `"nodes"`: An array with all nodes. Each node is an object with the
+# following properties:
+#      * `"labels"`: An array of all applied labels.
+#      * `"properties"`: An object mapping each defined property to its value.
+# * `"edges"`: An array with all relationships. Each relationship is an object
+# with the following properties:
+#      * `"type"`: The type (`String`) of the relationship.
+#      * `"properties"`: An object mapping each defined property to its value.
+#      * `"from"`: The local ID of the source node.
+#      * `"to"`: The local ID of the destination node.
+#
+# ~~~nit
+# import neo4j::graph::sequential_id
+#
+# var graph = new NeoGraph(new SequentialNodeCollection("nid"))
+# var a = new NeoNode
+# a.labels.add "Foo"
+# a["answer"] = 42
+# a["Ultimate question of"] = new JsonArray.from(["life",
+#              "the Universe", "and Everything."])
+# graph.nodes.register a
+# var b = graph.create_node
+# b.labels.add "Foo"
+# b.labels.add "Bar"
+# graph.edges.add new NeoEdge(a, "BAZ", b)
+#
+# var ostream = new StringOStream
+# var store = new JsonGraphStore(graph)
+# store.ostream = ostream
+# store.save
+# assert ostream.to_s == """{"nodes":[""" + """
+# {"labels":["Foo"],"properties":{"answer":42,""" + """
+# "Ultimate question of":["life","the Universe","and Everything."],""" + """
+# "nid":1}},""" + """
+# {"labels":["Foo","Bar"],"properties":{"nid":2}}],""" + """
+# "edges":[{"type":"BAZ","properties":{},"from":1,"to":2}]}"""
+#
+# graph.nodes.clear
+# graph.edges.clear
+# store.istream = new StringIStream(ostream.to_s)
+# store.load
+# assert 1 == graph.edges.length
+# for edge in graph.edges do
+#      assert "BAZ" == edge.rel_type
+#      assert a.labels == edge.from.labels
+#      for k, v in a.properties do assert v == edge.from.properties[k]
+#      assert b.labels == edge.to.labels
+#      for k, v in b.properties do assert v == edge.to.properties[k]
+# end
+# assert 2 == graph.nodes.length
+# ~~~
+class JsonGraphStore
+       super GraphStore
+
+       # The stream to use for `load`.
+       var istream: nullable IStream = null is writable
+
+       # The stream to use for `save` and `save_part`.
+       var ostream: nullable OStream = null is writable
+
+       # Use the specified `IOStream`.
+       init from_io(graph: NeoGraph, iostream: IOStream) do
+               init(graph)
+               istream = iostream
+               ostream = iostream
+       end
+
+       # Use the specified string to load the graph.
+       init from_string(graph: NeoGraph, string: String) do
+               init(graph)
+               istream = new StringIStream(string)
+       end
+
+       redef fun isolated_save do return true
+
+       redef fun load do
+               var istream = self.istream
+               assert istream isa IStream
+               fire_started
+               graph.load_json(istream.read_all)
+               fire_done
+       end
+
+       redef fun save_part(nodes, edges) do
+               var ostream = self.ostream
+               assert ostream isa OStream
+               fire_started
+               ostream.write(graph.to_json)
+               fire_done
+       end
+end
+
+redef class NeoGraph
+       super Jsonable
+
+       # Retrieve the graph from the specified JSON document.
+       #
+       # For the expected format, see `JsonGraphStore`.
+       #
+       # ~~~nit
+       # import neo4j::graph::sequential_id
+       #
+       # var graph = new NeoGraph(new SequentialNodeCollection("node_id"))
+       # var a = new NeoNode
+       # a.labels.add "Foo"
+       # a["answer"] = 42
+       # a["Ultimate question of"] = new JsonArray.from(["life",
+       #               "the Universe", "and Everything."])
+       # graph.nodes.register a
+       # var b = graph.create_node
+       # b.labels.add "Foo"
+       # b.labels.add "Bar"
+       # graph.edges.add new NeoEdge(a, "BAZ", b)
+       #
+       # graph = new NeoGraph.from_json(
+       #               new SequentialNodeCollection("node_id"), graph.to_json)
+       # assert 1 == graph.edges.length
+       # for edge in graph.edges do
+       #       assert "BAZ" == edge.rel_type
+       #       assert a.labels == edge.from.labels
+       #       for k, v in a.properties do assert v == edge.from.properties[k]
+       #       assert b.labels == edge.to.labels
+       #       for k, v in b.properties do assert v == edge.to.properties[k]
+       # end
+       # assert 2 == graph.nodes.length
+       # ~~~
+       init from_json(nodes: NeoNodeCollection, t: Text) do
+               from_json_object(nodes, t.parse_json.as(JsonObject))
+       end
+
+       # Retrieve the graph from the specified JSON object.
+       #
+       # For the expected format, see `JsonGraphStore`.
+       init from_json_object(nodes: NeoNodeCollection, o: JsonObject) do
+               init(nodes)
+               load_json_object(o)
+       end
+
+       # Retrieve a part of the graph from the specified JSON document.
+       #
+       # For the expected format, see `JsonGraphStore`.
+       fun load_json(t: Text) do
+               load_json_object(t.parse_json.as(JsonObject))
+       end
+
+       # Retrieve a part of the graph from the specified JSON object.
+       #
+       # For the expected format, see `JsonGraphStore`.
+       fun load_json_object(o: JsonObject) do
+               var json_nodes = o["nodes"].as(JsonArray)
+               var nodes = self.nodes
+               nodes.enlarge(nodes.length)
+               for json_node in json_nodes do
+                       assert json_node isa JsonObject
+                       var node = new NeoNode.from_json_object(json_node)
+                       nodes.add node
+               end
+
+               var json_edges = o["edges"].as(JsonArray)
+               var edges = self.edges
+               if edges isa AbstractArray[NeoEdge] then edges.enlarge(edges.length)
+               for json_edge in json_edges do
+                       assert json_edge isa JsonObject
+                       var from = nodes[nodes.id_from_jsonable(json_edge["from"])]
+                       var to = nodes[nodes.id_from_jsonable(json_edge["to"])]
+                       var rel_type = json_edge["type"].as(String)
+                       var json_properties = json_edge["properties"].as(JsonObject)
+                       var edge = new NeoEdge(from, rel_type, to)
+                       edge.properties.recover_with(json_properties)
+                       edges.add edge
+               end
+       end
+
+       redef fun to_json do return to_json_by_append
+
+       # Append the JSON representation of `self` to the specified buffer.
+       #
+       # For a description of the format, see `JsonGraphStore`.
+       #
+       # SEE: `to_json`
+       redef fun append_json(b) do
+               b.append "\{\"nodes\":["
+               append_entities_json(nodes, b)
+               b.append "],\"edges\":["
+               append_entities_json(edges, b)
+               b.append "]\}"
+       end
+
+       # Encode `self` in JSON.
+       #
+       # For a description of the format, see `JsonGraphStore`.
+       #
+       # SEE: `append_json`
+       private fun append_entities_json(entities: Collection[NeoEntity],
+                       b: Buffer) do
+               var i = entities.iterator
+               if i.is_ok then
+                       i.item.append_json_for(self, b)
+                       i.next
+                       for entity in i do
+                               b.add ','
+                               entity.append_json_for(self, b)
+                       end
+               end
+       end
+end
+
+redef class NeoNodeCollection
+       # Convert the specified JSON value into a local ID.
+       fun id_from_jsonable(id: nullable Jsonable): ID_TYPE do return id.as(ID_TYPE)
+end
+
+redef class NeoEntity
+
+       # Append the JSON representation of the entity to the specified buffer.
+       fun append_json_for(graph: NeoGraph, buffer: Buffer) is abstract
+end
+
+# Make `NeoNode` `Jsonable`.
+redef class NeoNode
+       super Jsonable
+
+       # Retrieve the node from the specified JSON value.
+       #
+       # Note: Here, the `"id"` is optional and ignored.
+       #
+       # SEE: `JsonGraph`
+       #
+       #     var node = new NeoNode.from_json("""
+       #     {
+       #       "labels": ["foo", "Bar"],
+       #       "properties": {
+       #               "baz": 42
+       #       }
+       #     }
+       #     """)
+       #     assert ["foo", "Bar"] == node.labels
+       #     assert 42 == node["baz"]
+       init from_json(t: Text) do
+               from_json_object(t.parse_json.as(JsonObject))
+       end
+
+       # Retrieve the node from the specified JSON value.
+       #
+       # Note: Here, the `"id"` is optional and ignored.
+       #
+       # SEE: `JsonGraph`
+       init from_json_object(o: JsonObject) do
+               init
+               var labels = o["labels"].as(JsonArray)
+               for lab in labels do self.labels.add(lab.as(String))
+               var json_properties = o["properties"].as(JsonObject)
+               properties.recover_with(json_properties)
+       end
+
+       redef fun to_json do return to_json_by_append
+
+       # Append the JSON representation of the node to the specified buffer.
+       #
+       # SEE: `JsonGraph`
+       redef fun append_json(b) do
+               b.append "\{\"labels\":["
+               var i = labels.iterator
+               if i.is_ok then
+                       i.item.append_json(b)
+                       i.next
+                       for lab in i do
+                               b.add ','
+                               lab.append_json(b)
+                       end
+               end
+               b.append "],\"properties\":"
+               properties.append_json(b)
+               b.add '}'
+       end
+
+       redef fun to_s do return to_json
+
+       # Append the JSON representation of the node to the specified buffer.
+       redef fun append_json_for(graph: NeoGraph, buffer: Buffer) do
+               append_json(buffer)
+       end
+end
+
+redef class NeoEdge
+
+       # Append the JSON representation of the relationship to the specified buffer.
+       #
+       # Use the IDs specfied by `graph.nodes`.
+       redef fun append_json_for(graph: NeoGraph, buffer: Buffer) do
+               buffer.append "\{\"type\":"
+               rel_type.append_json(buffer)
+               buffer.append ",\"properties\":"
+               properties.append_json(buffer)
+               buffer.append ",\"from\":"
+               graph.nodes.id_of(from).append_json(buffer)
+               buffer.append ",\"to\":"
+               graph.nodes.id_of(to).append_json(buffer)
+               buffer.append "}"
+       end
+end
diff --git a/lib/neo4j/graph/sequential_id.nit b/lib/neo4j/graph/sequential_id.nit
new file mode 100644 (file)
index 0000000..fb6129f
--- /dev/null
@@ -0,0 +1,116 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Provides a sequential identification scheme for Neo4j nodes.
+module neo4j::graph::sequential_id
+
+import graph
+private import pipeline
+
+
+# A Neo4j node collection using a sequential identification scheme.
+#
+# The local IDs are sequential numbers (integers) starting at `1`.
+#
+# Note: When loading nodes, the local IDs should forms a mostly contiguous
+# range starting at `1`. Else, this collection will consume a lot of memory.
+# Futhermore, the local IDs **must** be positive.
+#
+# ~~~nit
+# var nodes = new SequentialNodeCollection("id")
+# var a = nodes.create_node
+# var b = new NeoNode
+# var c = new NeoNode
+#
+# nodes.register b
+# c["id"] = 4
+# nodes.add c
+# assert a["id"] == 1
+# assert b["id"] == 2
+# assert c["id"] == 4
+# assert nodes.to_a == [a, b, c]
+# assert nodes.length == 3
+#
+# nodes.compact
+# assert a["id"] == 1
+# assert b["id"] == 2
+# assert c["id"] == 3
+# assert nodes.to_a == [a, b, c]
+# assert nodes.length == 3
+# ~~~
+class SequentialNodeCollection
+       super NeoNodeCollection
+
+       redef type ID_TYPE: Int
+
+       private var nodes = new Array[nullable NeoNode]
+
+       redef var length = 0
+
+       redef fun iterator do return new NullSkipper[NeoNode](self.nodes.iterator)
+
+       redef fun [](id) do return nodes[id].as(NeoNode)
+
+       redef fun get_or_null(id) do
+               if id < 0 or id > nodes.length then return null
+               return nodes[id]
+       end
+
+       redef fun has_id(id: Int): Bool do
+               return id >= 0 and id < nodes.length and nodes[id] isa NeoNode
+       end
+
+       redef fun enlarge(cap) do nodes.enlarge(cap)
+
+       redef fun register(node) do
+               nodes.add node
+               id_of(node) = nodes.length
+               length += 1
+       end
+
+       redef fun add(node) do
+               var id = node[id_property]
+               assert id isa Int else
+                       sys.stderr.write "The local ID must be an `Int`.\n"
+               end
+               assert id >= 0 else
+                       sys.stderr.write "The local ID must be greater or equal to 0. Got {id}.\n"
+               end
+               # Pad with nulls.
+               nodes.enlarge(id)
+               var delta = id - nodes.length
+               while delta > 0 do
+                       nodes.add null
+                       delta -= 1
+               end
+               nodes[id] = node
+               length += 1
+       end
+
+       redef fun remove_at(id) do
+               nodes[id] = null
+               length -= 1
+       end
+
+       redef fun clear do
+               nodes.clear
+               length = 0
+       end
+
+       redef fun compact do
+               var i = iterator
+
+               nodes = new Array[nullable NeoNode]
+               for n in i do
+                       nodes.add n
+                       id_of(n) = nodes.length
+               end
+       end
+end
diff --git a/lib/neo4j/json_store.nit b/lib/neo4j/json_store.nit
deleted file mode 100644 (file)
index cf9f441..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# This file is free software, which comes along with NIT. This software is
-# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-# PARTICULAR PURPOSE. You can modify it is you want, provided this header
-# is kept unaltered, and a notification of the changes is added.
-# You are allowed to redistribute it and sell it, alone or is a part of
-# another product.
-
-# Uses JSON as a storage medium for a Neo4j subgraph.
-module neo4j::json_store
-
-import neo4j
-private import template
-
-# A Neo4j graph that uses as a storage medium.
-#
-# The graph is stored as a JSON object with the following properties:
-#
-# * `"nodes"`: An array with all nodes. Each node is an object with the
-# following properties:
-#      * `"id"`: The ID (`Int`) that uniquely identifies the node in the current
-#      graph.
-#      * `"labels"`: An array of all applied labels.
-#      * `"properties"`: An object mapping each defined property to its value.
-# * `"links"`: An array with all relationships. Each relationship is an object
-# with the following properties:
-#      * `"type"`: The type (`String`) of the relationship.
-#      * `"properties"`: An object mapping each defined property to its value.
-#      * `"from"`: The ID (`Int`) of the source node.
-#      * `"to"`: The ID (`Int`) of the destination node.
-#
-# TODO Refine the graph API instead when it will be available.
-class JsonGraph
-       super Jsonable
-
-       # All nodes in the graph.
-       var nodes: SimpleCollection[NeoNode] = new Array[NeoNode]
-
-       # All relationships in the graph.
-       var links: SimpleCollection[NeoEdge] = new Array[NeoEdge]
-
-       # Create an empty graph.
-       init do end
-
-       # Retrieve the graph from the specified JSON value.
-       #
-       #     var graph = new JsonGraph
-       #     var a = new NeoNode
-       #     a.labels.add "Foo"
-       #     a["answer"] = 42
-       #     a["Ultimate question of"] = new JsonArray.from(["life",
-       #               "the Universe", "and Everything."])
-       #     graph.nodes.add a
-       #     var b = new NeoNode
-       #     b.labels.add "Foo"
-       #     b.labels.add "Bar"
-       #     graph.nodes.add b
-       #     graph.links.add new NeoEdge(a, "BAZ", b)
-       #     #
-       #     graph = new JsonGraph.from_json(graph.to_json)
-       #     assert 1 == graph.links.length
-       #     for link in graph.links do
-       #       assert "BAZ" == link.rel_type
-       #       assert a.labels == link.from.labels
-       #       for k, v in a.properties do assert v == link.from.properties[k]
-       #       assert b.labels == link.to.labels
-       #       for k, v in b.properties do assert v == link.to.properties[k]
-       #     end
-       #     assert 2 == graph.nodes.length
-       init from_json(t: Text) do
-               from_json_object(t.parse_json.as(JsonObject))
-       end
-
-       # Retrieve the graph from the specified JSON object.
-       init from_json_object(o: JsonObject) do
-               var node_by_id = new HashMap[Int, NeoNode]
-               var nodes = o["nodes"].as(JsonArray)
-               for json_node in nodes do
-                       assert json_node isa JsonObject
-                       var node = new NeoNode.from_json_object(json_node)
-                       node_by_id[json_node["id"].as(Int)] = node
-                       self.nodes.add node
-               end
-               var links = o["links"].as(JsonArray)
-               for json_link in links do
-                       assert json_link isa JsonObject
-                       var from = node_by_id[json_link["from"].as(Int)]
-                       var to = node_by_id[json_link["to"].as(Int)]
-                       var rel_type = json_link["type"].as(String)
-                       var json_properties = json_link["properties"].as(JsonObject)
-                       var link = new NeoEdge(from, rel_type, to)
-                       link.properties.recover_with(json_properties)
-                       self.links.add link
-               end
-       end
-
-       redef fun to_json do
-               var t = new Template
-               t.add "\{\"nodes\":["
-               var i = 0
-               for n in nodes do
-                       if i > 0 then t.add ","
-                       t.add n.to_json
-                       i += 1
-               end
-               t.add "],\"links\":["
-               i = 0
-               for link in links do
-                       if i > 0 then t.add ","
-                       t.add link.to_json
-                       i += 1
-               end
-               t.add "]\}"
-               return t.write_to_string
-       end
-end
-
-# Make `NeoNode` `Jsonable`.
-redef class NeoNode
-       super Jsonable
-
-       # Retrieve the node from the specified JSON value.
-       #
-       # Note: Here, the `"id"` is optional and ignored.
-       #
-       # SEE: `JsonGraph`
-       #
-       #     var node = new NeoNode.from_json("""
-       #     {
-       #       "labels": ["foo", "Bar"],
-       #       "properties": {
-       #               "baz": 42
-       #       }
-       #     }
-       #     """)
-       #     assert ["foo", "Bar"] == node.labels
-       #     assert 42 == node["baz"]
-       init from_json(t: Text) do
-               from_json_object(t.parse_json.as(JsonObject))
-       end
-
-       # Retrieve the node from the specified JSON value.
-       #
-       # Note: Here, the `"id"` is optional and ignored.
-       #
-       # SEE: `JsonGraph`
-       init from_json_object(o: JsonObject) do
-               init
-               var labels = o["labels"].as(JsonArray)
-               for lab in labels do self.labels.add(lab.as(String))
-               var json_properties = o["properties"].as(JsonObject)
-               properties.recover_with(json_properties)
-       end
-
-       # Get the JSON representation of `self`.
-       #
-       # SEE: `JsonGraph`
-       redef fun to_json do
-               var t = new Template
-               t.add "\{\"id\":"
-               t.add object_id.to_json
-               t.add ",\"labels\":["
-               var i = 0
-               for lab in labels do
-                       if i > 0 then t.add ","
-                       t.add lab.to_json
-                       i += 1
-               end
-               t.add "],\"properties\":"
-               t.add properties.to_json
-               t.add "}"
-               return t.write_to_string
-       end
-
-       redef fun to_s do return to_json
-end
-
-# Make `NeoEdge` `Jsonable`.
-redef class NeoEdge
-       super Jsonable
-
-       redef fun to_json do
-               var t = new Template
-               t.add "\{\"type\":"
-               t.add rel_type.to_json
-               t.add ",\"properties\":"
-               t.add properties.to_json
-               t.add ",\"from\":"
-               t.add from.object_id.to_json
-               t.add ",\"to\":"
-               t.add to.object_id.to_json
-               t.add "}"
-               return t.write_to_string
-       end
-
-       redef fun to_s do return to_json
-end
index 9c5ec01..31c0a45 100644 (file)
@@ -374,10 +374,10 @@ end
 
 private class DephIterator
        super Iterator[Node]
+
        var stack = new List[Iterator[nullable Node]]
 
-       init(i: Iterator[nullable Node])
-       do
+       init(i: Iterator[nullable Node]) is old_style_init do
                stack.add i
        end
 
index 8c8f9ea..fa5ce15 100644 (file)
@@ -44,11 +44,13 @@ abstract class Option
        var default_value: VALUE is writable
 
        # Create a new option
-       init(help: String, default: VALUE, names: nullable Array[String])
-       do
+       init(help: String, default: VALUE, names: nullable Array[String]) is old_style_init do
                init_opt(help, default, names)
        end
 
+       # Init option `helptext`, `default_value` and `names`.
+       #
+       # Also set current `value` to `default`.
        fun init_opt(help: String, default: VALUE, names: nullable Array[String])
        do
                if names == null then
@@ -80,6 +82,7 @@ abstract class Option
                return text.to_s
        end
 
+       # Pretty print the default value.
        fun pretty_default: String
        do
                var dv = default_value
@@ -97,7 +100,9 @@ end
 # Not really an option. Just add a line of text when displaying the usage
 class OptionText
        super Option
-       init(text: String) do super(text, null, null)
+
+       # Init a new OptionText with `text`.
+       init(text: String) is old_style_init do super(text, null, null)
 
        redef fun pretty(off) do return to_s
 
@@ -109,7 +114,8 @@ class OptionBool
        super Option
        redef type VALUE: Bool
 
-       init(help: String, names: String...) do super(help, false, names)
+       # Init a new OptionBool with a `help` message and `names`.
+       init(help: String, names: String...) is old_style_init do super(help, false, names)
 
        redef fun read_param(it)
        do
@@ -123,7 +129,8 @@ class OptionCount
        super Option
        redef type VALUE: Int
 
-       init(help: String, names: String...) do super(help, 0, names)
+       # Init a new OptionCount with a `help` message and `names`.
+       init(help: String, names: String...) is old_style_init do super(help, 0, names)
 
        redef fun read_param(it)
        do
@@ -135,6 +142,8 @@ end
 # Option with one parameter (mandatory by default)
 abstract class OptionParameter
        super Option
+
+       # Convert `str` to a value of type `VALUE`.
        protected fun convert(str: String): VALUE is abstract
 
        # Is the parameter mandatory?
@@ -159,7 +168,8 @@ class OptionString
        super OptionParameter
        redef type VALUE: nullable String
 
-       init(help: String, names: String...) do super(help, null, names)
+       # Init a new OptionString with a `help` message and `names`.
+       init(help: String, names: String...) is old_style_init do super(help, null, names)
 
        redef fun convert(str) do return str
 end
@@ -170,10 +180,14 @@ end
 class OptionEnum
        super OptionParameter
        redef type VALUE: Int
+
+       # Values in the enumeration.
        var values: Array[String]
 
-       init(values: Array[String], help: String, default: Int, names: String...)
-       do
+       # Init a new OptionEnum from `values` with a `help` message and `names`.
+       #
+       # `default` is the index of the default value in `values`.
+       init(values: Array[String], help: String, default: Int, names: String...) is old_style_init do
                assert values.length > 0
                self.values = values.to_a
                super("{help} <{values.join(", ")}>", default, names)
@@ -190,6 +204,7 @@ class OptionEnum
                return id
        end
 
+       # Get the value name from `values`.
        fun value_name: String do return values[value]
 
        redef fun pretty_default
@@ -203,7 +218,10 @@ class OptionInt
        super OptionParameter
        redef type VALUE: Int
 
-       init(help: String, default: Int, names: String...) do super(help, default, names)
+       # Init a new OptionInt with a `help` message, a `default` value and `names`.
+       init(help: String, default: Int, names: String...) is old_style_init do
+               super(help, default, names)
+       end
 
        redef fun convert(str) do return str.to_i
 end
@@ -213,7 +231,10 @@ class OptionFloat
        super OptionParameter
        redef type VALUE: Float
 
-       init(help: String, default: Float, names: String...) do super(help, default, names)
+       # Init a new OptionFloat with a `help` message, a `default` value and `names`.
+       init(help: String, default: Float, names: String...) is old_style_init do
+               super(help, default, names)
+       end
 
        redef fun convert(str) do return str.to_f
 end
@@ -224,8 +245,8 @@ class OptionArray
        super OptionParameter
        redef type VALUE: Array[String]
 
-       init(help: String, names: String...)
-       do
+       # Init a new OptionArray with a `help` message and `names`.
+       init(help: String, names: String...) is old_style_init do
                values = new Array[String]
                super(help, values, names)
        end
@@ -352,6 +373,7 @@ class OptionContext
                end
        end
 
+       # Options parsing errors.
        fun get_errors: Array[String]
        do
                var errors = new Array[String]
index be553ad..0ca08b2 100644 (file)
@@ -159,6 +159,45 @@ redef interface Iterator[E]
        end
 end
 
+# Wraps an iterator to skip nulls.
+#
+# ~~~nit
+# var i: Iterator[Int]
+#
+# i = new NullSkipper[Int]([null, 1, null, 2, null: nullable Int].iterator)
+# assert i.to_a == [1, 2]
+#
+# i = new NullSkipper[Int]([1, null, 2, 3: nullable Int].iterator)
+# assert i.to_a == [1, 2, 3]
+# ~~~
+class NullSkipper[E: Object]
+       super Iterator[E]
+
+       # The inner iterator.
+       var inner: Iterator[nullable E]
+
+       redef fun finish do inner.finish
+
+       redef fun is_ok do
+               skip_nulls
+               return inner.is_ok
+       end
+
+       redef fun item do
+               skip_nulls
+               return inner.item.as(E)
+       end
+
+       redef fun next do
+               inner.next
+               skip_nulls
+       end
+
+       private fun skip_nulls do
+               while inner.is_ok and inner.item == null do inner.next
+       end
+end
+
 # Interface that reify a function.
 # Concrete subclasses must implements the `apply` method.
 #
diff --git a/lib/progression.nit b/lib/progression.nit
new file mode 100644 (file)
index 0000000..b5d7f96
--- /dev/null
@@ -0,0 +1,68 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Event-based interface to track the progression of an operation.
+module progression
+
+# An operation that is trackable using a `ProgressionListener`.
+abstract class Trackable
+
+       # Listen to the progression of the operation.
+       var progression_listeners: SimpleCollection[ProgressionListener] =
+                       new Array[ProgressionListener]
+
+       # Notice the registered `ProgessionListener` that the operation started.
+       protected fun fire_started do
+               for l in progression_listeners do
+                       l.started
+                       l.progressed(0)
+               end
+       end
+
+       # Notice the registered `ProgessionListener` that the operation progressed.
+       #
+       # Parameter:
+       #
+       # * `done_part`: Indicates what is done.
+       # * `total`: Indicates what need to be done, `done_part` included.
+       protected fun fire_progressed(done_part: Int, total: Int) do
+               for l in progression_listeners do
+                       l.progressed(done_part * l.progression_max / total)
+               end
+       end
+
+       # Notice the registered `ProgessionListener` that the operation is done.
+       protected fun fire_done do
+               for l in progression_listeners do
+                       l.progressed(l.progression_max)
+                       l.done
+               end
+       end
+end
+
+# Listens to the progression of a possibly long-running operation.
+interface ProgressionListener
+       # The number that represents a completed operation.
+       fun progression_max: Int do return 100
+
+       # The operation started.
+       fun started do end
+
+       # The operation progressed.
+       #
+       # Parameter:
+       #
+       # * `progression`: Indicator of the progession, between `0` and
+       # `progression_max`.
+       fun progressed(progression: Int) do end
+
+       # The operation is done.
+       fun done do end
+end
index b01c3e5..aa567e8 100644 (file)
@@ -48,9 +48,16 @@ class Sprite
        # height of the sprite
        var height: Int = 100 is writable
 
+       # X coordinate of left side.
        fun left: Int do return x - width/2
+
+       # X coordinate of right side.
        fun right: Int do return x + width/2
+
+       # Y coordinate of top.
        fun top: Int do return y - height/2
+
+       # Y coordinate of bottom.
        fun bottom: Int do return y + height/2
 
        # x velocity (applied by `update')
@@ -102,10 +109,6 @@ class LiveGroup[E: LiveObject]
        super LiveObject
        super List[E]
 
-       init
-       do
-       end
-
        # Recursively update each live objects that `exists'
        redef fun update
        do
index 26d9937..77acfbe 100644 (file)
@@ -66,7 +66,11 @@ class TCPStream
                        closed = true
                        return
                end
-               socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1)
+               if not socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
+                       end_reached = true
+                       closed = true
+                       return
+               end
                var hostname = socket.gethostbyname(host)
                addrin = new NativeSocketAddrIn.with_hostent(hostname, port)
 
@@ -174,8 +178,10 @@ class TCPStream
        # Send the data present in the socket buffer
        fun flush
        do
-               socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1)
-               socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0)
+               if not socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
+                  not socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
+                       closed = true
+               end
        end
 end
 
@@ -193,7 +199,10 @@ class TCPServer
                socket = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
                        new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
                assert not socket.address_is_null
-               socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1)
+               if not socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
+                       closed = true
+                       return
+               end
                addrin = new NativeSocketAddrIn.with(port, new NativeSocketAddressFamilies.af_inet)
                address = addrin.address
 
index 71867b9..44aff9e 100644 (file)
@@ -37,6 +37,7 @@ in "C" `{
 
 # Wrapper for the data structure PollFD used for polling on a socket
 class PollFD
+       super FinalizableOnce
 
        # The PollFD object
        private var poll_struct: NativeSocketPollFD
@@ -76,27 +77,30 @@ class PollFD
                return response & mask;
        `}
 
+       redef fun finalize_once
+       do
+               poll_struct.free
+       end
 end
 
 # Data structure used by the poll function
-private extern class NativeSocketPollFD `{ struct pollfd `}
+private extern class NativeSocketPollFD `{ struct pollfd * `}
 
-       # File descriptor id
-       private fun fd: Int `{ return recv.fd; `}
+       # File descriptor
+       fun fd: Int `{ return recv->fd; `}
 
        # List of events to be watched
-       private fun events: Int `{ return recv.events; `}
+       fun events: Int `{ return recv->events; `}
 
        # List of events received by the last poll function
-       private fun revents: Int `{  return recv.revents; `}
+       fun revents: Int `{  return recv->revents; `}
 
        new (pid: Int, events: NativeSocketPollValues) `{
-               struct pollfd poll;
-               poll.fd = pid;
-               poll.events = events;
+               struct pollfd *poll = malloc(sizeof(struct pollfd));
+               poll->fd = pid;
+               poll->events = events;
                return poll;
        `}
-
 end
 
 extern class NativeSocket `{ int* `}
@@ -141,12 +145,14 @@ extern class NativeSocket `{ int* `}
        `}
 
        # Sets an option for the socket
-       fun setsockopt(level: NativeSocketOptLevels, option_name: NativeSocketOptNames, option_value: Int) `{
+       #
+       # Returns `true` on success.
+       fun setsockopt(level: NativeSocketOptLevels, option_name: NativeSocketOptNames, option_value: Int): Bool `{
                int err = setsockopt(*recv, level, option_name, &option_value, sizeof(int));
                if(err != 0){
-                       perror("Error on setsockopts: ");
-                       exit(1);
+                       return 0;
                }
+               return 1;
        `}
 
        fun bind(addrIn: NativeSocketAddrIn): Int `{ return bind(*recv, (struct sockaddr*)addrIn, sizeof(*addrIn)); `}
@@ -177,7 +183,7 @@ extern class NativeSocket `{ int* `}
        # The array's members are pollfd structures within which fd specifies an open file descriptor and events and revents are bitmasks constructed by
        # OR'ing a combination of the pollfd flags.
        private fun native_poll(filedesc: NativeSocketPollFD, timeout: Int): Int `{
-               int poll_return = poll(&filedesc, 1, timeout);
+               int poll_return = poll(filedesc, 1, timeout);
                return poll_return;
        `}
 
@@ -368,9 +374,6 @@ extern class NativeSocketAddressFamilies `{ int `}
        # Novell Internet Protocol
        new af_ipx `{ return AF_IPX; `}
 
-       # Integrated Services Digital Network
-       new af_isdn `{ return AF_ISDN; `}
-
        # IPv6
        new af_inet6 `{ return AF_INET6; `}
 
@@ -387,7 +390,6 @@ extern class NativeSocketProtocolFamilies `{ int `}
        new pf_decnet `{ return PF_DECnet; `}
        new pf_route `{ return PF_ROUTE; `}
        new pf_ipx `{ return PF_IPX; `}
-       new pf_isdn `{ return PF_ISDN; `}
        new pf_key `{ return PF_KEY; `}
        new pf_inet6 `{ return PF_INET6; `}
        new pf_max `{ return PF_MAX; `}
index acee1f6..23fa47f 100644 (file)
@@ -36,3 +36,29 @@ class Finalizable
        # to use attributes of this instances.
        fun finalize do end
 end
+
+# An object to be finalized only once
+#
+# This is an utility sub-class to `Finalizable` which ensures that `finalized_once`
+# is called only once per instance. User classes implementing `FinalizableOnce`
+# shoud specialize `finalize_once` and _not_ `finalize`. When manipulating the user
+# class, only `finalize` should be called as it protects `finalize_once`.
+class FinalizableOnce
+       super Finalizable
+
+       # Has `self` been finalized? (either by the GC or an explicit call to `finalize`)
+       var finalized = false
+
+       redef fun finalize
+       do
+               if finalized then return
+
+               finalize_once
+               finalized = true
+       end
+
+       # Real finalization method of `FinalizableOnce`, will be called only once
+       #
+       # See: `Finalizable::finalize` for restrictions on finalizer methods.
+       protected fun finalize_once do end
+end
index 46ad73c..ddc01fe 100644 (file)
@@ -493,5 +493,12 @@ class StringIStream
                source = ""
        end
 
+       redef fun read_all do
+               var c = cursor
+               cursor = source.length
+               if c == 0 then return source
+               return source.substring_from(c)
+       end
+
        redef fun eof do return cursor >= source.length
 end
index 83641d1..f5b8786 100644 (file)
@@ -34,7 +34,7 @@ typedef struct {
 # UTF-8 char as defined in RFC-3629, e.g. 1-4 Bytes
 #
 # A UTF-8 char has its bytes stored in a NativeString (char*)
-extern class UnicodeChar `{ UTF8Char* `}
+extern class UTF8Char `{ UTF8Char* `}
 
        new(pos: Int, ns: NativeString) `{
                UTF8Char* u = malloc(sizeof(UTF8Char));
@@ -83,8 +83,8 @@ extern class UnicodeChar `{ UTF8Char* `}
        # Returns the Unicode code point representing the character
        #
        # Note : A unicode character might not be a visible glyph, but it will be used to determine canonical equivalence
-       fun code_point: Int import UnicodeChar.len `{
-               switch(UnicodeChar_len(recv)){
+       fun code_point: Int import UTF8Char.len `{
+               switch(UTF8Char_len(recv)){
                        case 1:
                                return (long)(0x7F & (unsigned char)recv->ns[recv->pos]);
                        case 2:
@@ -105,8 +105,8 @@ extern class UnicodeChar `{ UTF8Char* `}
        #
        # NOTE : Works only on ASCII chars
        # TODO : Support unicode for to_upper
-       fun to_upper: UnicodeChar import UnicodeChar.code_point `{
-               int cp = UnicodeChar_code_point(recv);
+       fun to_upper: UTF8Char import UTF8Char.code_point `{
+               int cp = UTF8Char_code_point(recv);
                if(cp < 97 || cp > 122){ return recv; }
                char* ns = malloc(2);
                ns[1] = '\0';
@@ -122,8 +122,8 @@ extern class UnicodeChar `{ UTF8Char* `}
        #
        # NOTE : Works only on ASCII chars
        # TODO : Support unicode for to_upper
-       fun to_lower: UnicodeChar import UnicodeChar.code_point `{
-               int cp = UnicodeChar_code_point(recv);
+       fun to_lower: UTF8Char import UTF8Char.code_point `{
+               int cp = UTF8Char_code_point(recv);
                if(cp < 65 || cp > 90){ return recv; }
                char* ns = malloc(2);
                ns[1] = '\0';
@@ -140,15 +140,15 @@ extern class UnicodeChar `{ UTF8Char* `}
                if o isa Char then
                        if len != 1 then return false
                        if code_point == o.ascii then return true
-               else if o isa UnicodeChar then
+               else if o isa UTF8Char then
                        if len != o.len then return false
                        if code_point == o.code_point then return true
                end
                return false
        end
 
-       redef fun output import UnicodeChar.code_point `{
-               switch(UnicodeChar_len(recv)){
+       redef fun output import UTF8Char.code_point `{
+               switch(UTF8Char_len(recv)){
                        case 1:
                                printf("%c", recv->ns[recv->pos]);
                                break;
@@ -165,7 +165,7 @@ extern class UnicodeChar `{ UTF8Char* `}
        `}
 
        redef fun to_s import NativeString.to_s_with_length `{
-               int len = utf8___UnicodeChar_len___impl(recv);
+               int len = utf8___UTF8Char_len___impl(recv);
                char* r = malloc(len + 1);
                r[len] = '\0';
                char* src = (recv->ns + recv->pos);
@@ -182,10 +182,10 @@ private extern class StringIndex `{ UTF8Char* `}
        new(size: Int) `{ return malloc(size*sizeof(UTF8Char)); `}
 
        # Sets the character at `index` as `item`
-       fun []=(index: Int, item: UnicodeChar) `{ recv[index] = *item; `}
+       fun []=(index: Int, item: UTF8Char) `{ recv[index] = *item; `}
 
        # Gets the character at position `id`
-       fun [](id: Int): UnicodeChar `{ return &recv[id]; `}
+       fun [](id: Int): UTF8Char `{ return &recv[id]; `}
 
        # Copies a part of self starting at index `my_from` of length `length` into `other`, starting at `its_from`
        fun copy_to(other: StringIndex, my_from: Int, its_from: Int, length: Int)`{
@@ -259,7 +259,7 @@ redef class FlatString
                        var uchar = index[i]
                        var uchar_len = uchar.len
                        ipos -= uchar_len
-                       new_index[pos_index] = new UnicodeChar(ipos, native)
+                       new_index[pos_index] = new UTF8Char(ipos, native)
                        pos_index -= 1
                        items.copy_to(native, uchar_len, pos, ipos)
                        pos += uchar_len
@@ -373,7 +373,7 @@ redef class NativeString
        # Creates the index for said NativeString
        # `length` is the size of the CString (in bytes, up to the first \0)
        # real_len is just a way to store the length (UTF-8 characters)
-       private fun make_index(length: Int, real_len: Container[Int]): StringIndex import Container[Int].item=, UnicodeChar.len `{
+       private fun make_index(length: Int, real_len: Container[Int]): StringIndex import Container[Int].item=, UTF8Char.len `{
                int pos = 0;
                int index_pos = 0;
                UTF8Char* index = malloc(length*sizeof(UTF8Char));
@@ -381,7 +381,7 @@ redef class NativeString
                        UTF8Char* curr = &index[index_pos];
                        curr->pos = pos;
                        curr->ns = recv;
-                       pos += UnicodeChar_len(curr);
+                       pos += UTF8Char_len(curr);
                        index_pos ++;
                }
                Container_of_Int_item__assign(real_len, index_pos);
index 1e9fb67..b865017 100644 (file)
@@ -32,8 +32,5 @@ end
 # A symbol is a unique immutable string
 class Symbol
        private var string: String
-       redef fun to_s do return _string.to_s
-
-       # Only used by String::to_symbol
-       private init(s: String) do _string = s
+       redef fun to_s do return string.to_s
 end
index 3f0e8a1..dfe463c 100644 (file)
@@ -1056,8 +1056,8 @@ abstract class AbstractCompilerVisitor
        # The current visited AST node
        var current_node: nullable ANode = null is writable
 
-       # The current `Frame`
-       var frame: nullable Frame = null is writable
+       # The current `StaticFrame`
+       var frame: nullable StaticFrame = null is writable
 
        # Alias for self.compiler.mainmodule.object_type
        fun object_type: MClassType do return self.compiler.mainmodule.object_type
@@ -1686,8 +1686,8 @@ class RuntimeVariable
        end
 end
 
-# A frame correspond to a visited property in a `GlobalCompilerVisitor`
-class Frame
+# The static context of a visited property in a `AbstractCompilerVisitor`
+class StaticFrame
 
        type VISITOR: AbstractCompilerVisitor
 
@@ -2286,7 +2286,7 @@ redef class AAttrPropdef
                var oldnode = v.current_node
                v.current_node = self
                var old_frame = v.frame
-               var frame = new Frame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv])
+               var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv])
                v.frame = frame
 
                var value
@@ -2325,7 +2325,7 @@ redef class AAttrPropdef
                var oldnode = v.current_node
                v.current_node = self
                var old_frame = v.frame
-               var frame = new Frame(v, self.mpropdef.as(not null), recv.mtype.as(MClassType), [recv])
+               var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mtype.as(MClassType), [recv])
                v.frame = frame
                # Force read to check the initialization
                v.read_attribute(self.mpropdef.mproperty, recv)
index 950125d..b7069e3 100644 (file)
@@ -176,7 +176,8 @@ $(call import-module,android/native_app_glue)
     <application
                android:label="@string/app_name"
                android:hasCode="true"
-               android:debuggable="{{{not release}}}">
+               android:debuggable="{{{not release}}}"
+               {{{icon_declaration}}}>
 
         <!-- Our activity is the built-in NativeActivity framework class.
              This will take care of integrating with our NDK code. -->
@@ -292,12 +293,40 @@ $(call import-module,android/native_app_glue)
                # Move the apk to the target
                var outname = outfile(compiler.mainmodule)
 
-               var src_apk_suffix
                if release then
-                       src_apk_suffix = "release-unsigned"
-               else src_apk_suffix = "debug"
+                       var apk_path = "{android_project_root}/bin/{compiler.mainmodule.name}-release-unsigned.apk"
 
-               toolcontext.exec_and_check(["mv", "{android_project_root}/bin/{compiler.mainmodule.name}-{src_apk_suffix}.apk", outname], "Android project error")
+                       # Sign APK
+                       var keystore_path= "KEYSTORE".environ
+                       var key_alias= "KEY_ALIAS".environ
+                       var tsa_server= "TSA_SERVER".environ
+
+                       if key_alias.is_empty then
+                               toolcontext.fatal_error(null,
+                                       "Fatal Error: the environment variable `KEY_ALIAS` must be set to use the `--release` option on Android projects.")
+                       end
+
+                       args = ["jarsigner", "-sigalg", "MD5withRSA", "-digestalg", "SHA1", apk_path, key_alias]
+
+                       ## Use a custom keystore
+                       if not keystore_path.is_empty then args.add_all(["-keystore", keystore_path])
+
+                       ## Use a TSA server
+                       if not tsa_server.is_empty then args.add_all(["-tsa", tsa_server])
+
+                       toolcontext.exec_and_check(args, "Android project error")
+
+                       # Clean output file
+                       if outname.to_path.exists then outname.to_path.delete
+
+                       # Align APK
+                       args = ["zipalign", "4", apk_path, outname]
+                       toolcontext.exec_and_check(args, "Android project error")
+               else
+                       # Move to the expected output path
+                       args = ["mv", "{android_project_root}/bin/{compiler.mainmodule.name}-debug.apk", outname]
+                       toolcontext.exec_and_check(args, "Android project error")
+               end
        end
 end
 
index e633e36..7a0f162 100644 (file)
@@ -972,7 +972,7 @@ private class CustomizedRuntimeFunction
                        selfvar.is_exact = true
                end
                var arguments = new Array[RuntimeVariable]
-               var frame = new Frame(v, mmethoddef, recv, arguments)
+               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
 
                var sig = new FlatBuffer
@@ -1031,7 +1031,7 @@ private class CustomizedRuntimeFunction
                        ret = v.resolve_for(ret, arguments.first)
                end
                if self.mmethoddef.can_inline(v) then
-                       var frame = new Frame(v, self.mmethoddef, self.recv, arguments)
+                       var frame = new StaticFrame(v, self.mmethoddef, self.recv, arguments)
                        frame.returnlabel = v.get_name("RET_LABEL")
                        if ret != null then
                                frame.returnvar = v.new_var(ret)
index 2d98fb6..68438c7 100644 (file)
@@ -1245,7 +1245,7 @@ class SeparateCompilerVisitor
                        (compiler.modelbuilder.toolcontext.opt_inline_some_methods.value and mmethoddef.can_inline(self)) then
                        compiler.modelbuilder.nb_invok_by_inline += 1
                        if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_inline++;")
-                       var frame = new Frame(self, mmethoddef, recvtype, arguments)
+                       var frame = new StaticFrame(self, mmethoddef, recvtype, arguments)
                        frame.returnlabel = self.get_name("RET_LABEL")
                        frame.returnvar = res
                        var old_frame = self.frame
@@ -1295,11 +1295,11 @@ class SeparateCompilerVisitor
                # of the method (ie recv) if the static type is unresolved
                # This is more complex than usual because the unresolved type must not be resolved
                # with the current receiver (ie self).
-               # Therefore to isolate the resolution from self, a local Frame is created.
+               # Therefore to isolate the resolution from self, a local StaticFrame is created.
                # One can see this implementation as an inlined method of the receiver whose only
                # job is to allocate the array
                var old_frame = self.frame
-               var frame = new Frame(self, mpropdef, mpropdef.mclassdef.bound_mtype, [recv])
+               var frame = new StaticFrame(self, mpropdef, mpropdef.mclassdef.bound_mtype, [recv])
                self.frame = frame
                #print "required Array[{elttype}] for recv {recv.inspect}. bound=Array[{self.resolve_for(elttype, recv)}]. selfvar={frame.arguments.first.inspect}"
                var res = self.array_instance(varargs, elttype)
@@ -1817,7 +1817,7 @@ class SeparateRuntimeFunction
                var v = compiler.new_visitor
                var selfvar = new RuntimeVariable("self", recv, recv)
                var arguments = new Array[RuntimeVariable]
-               var frame = new Frame(v, mmethoddef, recv, arguments)
+               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
 
                var msignature = mmethoddef.msignature.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true)
@@ -1889,7 +1889,7 @@ class VirtualRuntimeFunction
                var v = compiler.new_visitor
                var selfvar = new RuntimeVariable("self", v.object_type, recv)
                var arguments = new Array[RuntimeVariable]
-               var frame = new Frame(v, mmethoddef, recv, arguments)
+               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
 
                var sig = new FlatBuffer
index fc556d6..f8a1250 100644 (file)
@@ -543,8 +543,8 @@ abstract class NitdocPage
                                redef_article.title_classes.add "signature info"
                                redef_article.css_classes.add "nospace"
                                var redef_content = new Template
-                               if mpropdef.mdoc_or_fallback != null then
-                                       redef_content.add mpropdef.mdoc_or_fallback.tpl_comment
+                               if mpropdef.mdoc != null then
+                                       redef_content.add mpropdef.mdoc.tpl_comment
                                end
                                redef_article.content = redef_content
                                subarticle.add_child redef_article
index 6149ffa..58d3506 100644 (file)
@@ -508,6 +508,19 @@ redef class ModelBuilder
                        end
                end
 
+               # Check for conflicting module names in the project
+               if mgroup != null then
+                       var others = model.get_mmodules_by_name(mod_name)
+                       if others != null then for other in others do
+                               if other.mgroup!= null and other.mgroup.mproject == mgroup.mproject then
+                                       var node: ANode
+                                       if decl == null then node = nmodule else node = decl.n_name
+                                       error(node, "Error: A module named `{other.full_name}` already exists at {other.location}")
+                                       break
+                               end
+                       end
+               end
+
                # Create the module
                var mmodule = new MModule(model, mgroup, mod_name, nmodule.location)
                nmodule.mmodule = mmodule
index 3e3c497..e66f164 100644 (file)
@@ -129,12 +129,6 @@ class MModule
                                assert mgroup.default_mmodule == null
                                mgroup.default_mmodule = self
                        end
-                       # placebo for old module nesting hierarchy
-                       var direct_owner = mgroup.default_mmodule
-                       if direct_owner == self then
-                               # The potential owner is the default_mmodule of the parent group
-                               if mgroup.parent != null then direct_owner = mgroup.parent.default_mmodule
-                       end
                end
                self.in_importation = model.mmodule_importation_hierarchy.add_node(self)
        end
index 65840a8..8a49296 100644 (file)
@@ -97,6 +97,18 @@ redef class ModelBuilder
                                error(nclassdef, "Redef error: No imported class {name} to refine.")
                                return
                        end
+
+                       # Check for conflicting class full-names in the project
+                       if mmodule.mgroup != null and mvisibility >= protected_visibility then
+                               var mclasses = model.get_mclasses_by_name(name)
+                               if mclasses != null then for other in mclasses do
+                                       if other.intro_mmodule.mgroup != null and other.intro_mmodule.mgroup.mproject == mmodule.mgroup.mproject then
+                                               error(nclassdef, "Error: A class named `{other.full_name}` is already defined in module `{other.intro_mmodule}` at {other.intro.location}.")
+                                               break
+                                       end
+                               end
+                       end
+
                        mclass = new MClass(mmodule, name, names, mkind, mvisibility)
                        #print "new class {mclass}"
                else if nclassdef isa AStdClassdef and nmodule.mclass2nclassdef.has_key(mclass) then
index 0fd7acc..d8fd96d 100644 (file)
@@ -485,6 +485,18 @@ redef class APropdef
                                modelbuilder.error(self, "Redef error: {mclassdef.mclass}::{mprop.name} is an inherited property. To redefine it, add the redef keyword.")
                                return false
                        end
+
+                       # Check for full-name conflicts in the project.
+                       # A public property should have a unique qualified name `project::class::prop`.
+                       if mprop.intro_mclassdef.mmodule.mgroup != null and mprop.visibility >= protected_visibility then
+                               var others = modelbuilder.model.get_mproperties_by_name(mprop.name)
+                               if others != null then for other in others do
+                                       if other != mprop and other.intro_mclassdef.mmodule.mgroup != null and other.intro_mclassdef.mmodule.mgroup.mproject == mprop.intro_mclassdef.mmodule.mgroup.mproject and other.intro_mclassdef.mclass.name == mprop.intro_mclassdef.mclass.name and other.visibility >= protected_visibility then
+                                               modelbuilder.advice(self, "full-name-conflict", "Warning: A property named `{other.full_name}` is already defined in module `{other.intro_mclassdef.mmodule}` for the class `{other.intro_mclassdef.mclass.name}`.")
+                                               break
+                                       end
+                               end
+                       end
                else
                        if not need_redef then
                                modelbuilder.error(self, "Error: No property {mclassdef.mclass}::{mprop.name} is inherited. Remove the redef keyword to define a new property.")
index 71a8669..e092371 100644 (file)
@@ -20,7 +20,7 @@ import highlight
 import docdown
 
 redef class ModelBuilder
-       fun test_markdown(page: HTMLTag, mmodule: MModule)
+       fun do_test_markdown(page: HTMLTag, mmodule: MModule)
        do
                page.add_raw_html "<h3 id='{mmodule}'>module {mmodule}</h1>"
                var mdoc = mmodule.mdoc
@@ -115,13 +115,13 @@ if opt_full.value then
                                page.add mdoc.full_markdown
                        end
                        for m in g.mmodules do
-                               modelbuilder.test_markdown(page, m)
+                               modelbuilder.do_test_markdown(page, m)
                        end
                end
        end
 else
        for m in mmodules do
-               modelbuilder.test_markdown(page, m)
+               modelbuilder.do_test_markdown(page, m)
        end
 end
 
index 3e99de2..021c2c7 100644 (file)
@@ -242,28 +242,30 @@ class ToolContext
        #
        # Stops execution and prints errors if the program isn't available or didn't end correctly
        fun exec_and_check(args: Array[String], error: String)
-        do
-                var prog = args.first
-                args.remove_at 0
-
-                # Is the wanted program available?
-                var proc_which = new IProcess.from_a("which", [prog])
-                proc_which.wait
-                var res = proc_which.status
-                if res != 0 then
-                        print "{error}: executable \"{prog}\" not found"
-                        exit 1
-                end
-
-                # Execute the wanted program
-                var proc = new Process.from_a(prog, args)
-                proc.wait
-                res = proc.status
-                if res != 0 then
-                        print "{error}: execution of \"{prog} {args.join(" ")}\" failed"
-                        exit 1
-                end
-        end
+       do
+               info("+ {args.join(" ")}", 2)
+
+               var prog = args.first
+               args.remove_at 0
+
+               # Is the wanted program available?
+               var proc_which = new IProcess.from_a("which", [prog])
+               proc_which.wait
+               var res = proc_which.status
+               if res != 0 then
+                       print "{error}: executable \"{prog}\" not found"
+                       exit 1
+               end
+
+               # Execute the wanted program
+               var proc = new Process.from_a(prog, args)
+               proc.wait
+               res = proc.status
+               if res != 0 then
+                       print "{error}: execution of \"{prog} {args.join(" ")}\" failed"
+                       exit 1
+               end
+       end
 
        # Global OptionContext
        var option_context = new OptionContext