├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── modelgen ├── .gitignore ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── dkharrat │ │ └── nexusdata │ │ └── modelgen │ │ ├── ModelGenerator.java │ │ ├── Startup.java │ │ └── metamodel │ │ ├── Attribute.java │ │ ├── Entity.java │ │ ├── EnumProperty.java │ │ ├── Model.java │ │ ├── ModelWrapper.java │ │ ├── Property.java │ │ └── Relationship.java │ └── resources │ ├── logback.xml │ ├── templates │ ├── generated_model.ftl │ └── user_model.ftl │ └── version.properties ├── nexusdata ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ ├── assets │ │ ├── address.model.json │ │ ├── company.model.json │ │ └── logback.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── dkharrat │ │ │ └── nexusdata │ │ │ └── test │ │ │ ├── Address.java │ │ │ ├── Company.java │ │ │ ├── Contractor.java │ │ │ ├── Director.java │ │ │ ├── Employee.java │ │ │ ├── EntityTest.java │ │ │ ├── ManagedObjectTest.java │ │ │ ├── ObjectContextTest.java │ │ │ ├── ObjectContextWithInMemoryStoreTest.java │ │ │ ├── ObjectContextWithSqlStoreTest.java │ │ │ ├── ObjectModelTest.java │ │ │ ├── Passport.java │ │ │ ├── PersistentStoreCoordinatorTest.java │ │ │ ├── Person.java │ │ │ ├── PredicateParserTest.java │ │ │ ├── PredicatesTest.java │ │ │ ├── _Address.java │ │ │ ├── _Company.java │ │ │ ├── _Contractor.java │ │ │ ├── _Director.java │ │ │ ├── _Employee.java │ │ │ ├── _Passport.java │ │ │ └── _Person.java │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher.png │ │ ├── drawable-ldpi │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ └── values │ │ └── strings.xml │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── github │ └── dkharrat │ └── nexusdata │ ├── core │ ├── AtomicStore.java │ ├── ChangedObjectsSet.java │ ├── FaultingSet.java │ ├── FetchRequest.java │ ├── IncrementalStore.java │ ├── ManagedObject.java │ ├── NoSuchPropertyException.java │ ├── ObjectContext.java │ ├── ObjectContextNotifier.java │ ├── ObjectID.java │ ├── ObjectsChangedNotification.java │ ├── PersistentStore.java │ ├── PersistentStoreCoordinator.java │ ├── PersistentStoreRequest.java │ ├── SaveChangesRequest.java │ ├── SortDescriptor.java │ └── StoreCacheNode.java │ ├── metamodel │ ├── Attribute.java │ ├── Entity.java │ ├── ObjectModel.java │ ├── ObjectModelJsonParser.java │ ├── Property.java │ └── Relationship.java │ ├── predicate │ ├── ComparisonPredicate.java │ ├── CompoundPredicate.java │ ├── ConstantExpression.java │ ├── Expression.java │ ├── ExpressionBuilder.java │ ├── ExpressionVisitor.java │ ├── FieldPathExpression.java │ ├── NotPredicate.java │ ├── Predicate.java │ ├── PredicateBuilder.java │ ├── ThisExpression.java │ └── parser │ │ ├── ComparisonParselet.java │ │ ├── ConstantParselet.java │ │ ├── ExpressionNode.java │ │ ├── GroupParselet.java │ │ ├── InfixParselet.java │ │ ├── Lexer.java │ │ ├── LexerGrammar.java │ │ ├── LogicalParselet.java │ │ ├── NameParselet.java │ │ ├── ParseException.java │ │ ├── Parser.java │ │ ├── PredicateParser.java │ │ ├── PrefixParselet.java │ │ └── Token.java │ ├── store │ ├── AndroidSqlPersistentStore.java │ ├── DatabaseHelper.java │ ├── InMemoryPersistentStore.java │ ├── PredicateToSQL.java │ └── Utils.java │ └── utils │ ├── DateUtil.java │ ├── ObjectUtil.java │ ├── SqlTableBuilder.java │ ├── StreamUtil.java │ ├── StringUtil.java │ └── android │ ├── CursorUtil.java │ └── SQLiteDatabaseHelper.java ├── samples └── todo │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── todo.model.json │ ├── java │ └── org │ │ └── example │ │ └── todo │ │ ├── MainActivity.java │ │ ├── NewTaskActivity.java │ │ ├── Task.java │ │ ├── TodoApp.java │ │ ├── User.java │ │ ├── _Task.java │ │ └── _User.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-ldpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── layout │ ├── list_item_with_detail.xml │ ├── main.xml │ └── new_task.xml │ ├── menu │ └── form_actionbar.xml │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEA 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .idea/ 6 | local.properties 7 | out/ 8 | 9 | # Eclipse 10 | .classpath 11 | .project 12 | .settings/ 13 | 14 | # Android 15 | bin/ 16 | target 17 | gen/ 18 | 19 | # Mac 20 | .DS_Store 21 | 22 | # Maven 23 | log/ 24 | target/ 25 | 26 | # Gradle 27 | .gradle 28 | build 29 | bin 30 | 31 | # vi 32 | *.swp 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.2.1 5 | ----- 6 | * Support entity inheritence 7 | * Defining the inverse for a relationship is now optional 8 | * modelgen now supports file includes, which allows breaking up model into multiple files. 9 | * Bug fixes and performance improvements 10 | 11 | 0.1.3 12 | ----- 13 | * Fixed exception when using 'null' in comparisons (Issue #6). 14 | * Update project to use latest android-gradle settings. 15 | * Use Java generics for a ManagedObject-based collection. 16 | * Fixed crash when model does not define relationships. 17 | * Sources are now published to Maven repo too. 18 | 19 | 0.1.2 20 | ----- 21 | * Support 'null' keyword in predicates (e.g. "name == null") 22 | * Support querying for a specific object instance (currently, predicate can 23 | only be built via `ExpressionBuilder`, but not yet via expression parser) 24 | 25 | 0.1.1 26 | ----- 27 | * Add support for Double / Float types (be sure to use latest version of modelgen) 28 | 29 | 0.1.0 30 | ----- 31 | * Initial release. 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Thanks for considering to contribute! If you would like to contribute code you 5 | can do so through GitHub by forking the repository and sending a pull request. 6 | 7 | When submitting code, please make every effort to follow existing conventions 8 | and style in order to keep the code as readable as possible. Please also make 9 | sure your code compiles. 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.0.0' 7 | } 8 | } 9 | 10 | allprojects { 11 | repositories { 12 | jcenter() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /modelgen/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | bin 4 | out 5 | -------------------------------------------------------------------------------- /modelgen/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin:'application' 3 | 4 | version = '0.1.0' 5 | sourceCompatibility = 1.7 6 | mainClassName = "com.github.dkharrat.nexusdata.modelgen.Startup" 7 | jar { 8 | dependsOn configurations.runtime 9 | from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } } 10 | 11 | manifest { 12 | attributes 'Main-Class': mainClassName 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | compile 'org.freemarker:freemarker:2.3.22' 22 | compile 'commons-cli:commons-cli:1.2' 23 | compile 'com.google.code.gson:gson:2.3.1' 24 | compile 'org.slf4j:slf4j-api:1.7.12' 25 | compile 'ch.qos.logback:logback-core:1.1.3' 26 | compile 'ch.qos.logback:logback-classic:1.1.3' 27 | compile 'com.google.guava:guava:18.0' 28 | compile 'org.modeshape:modeshape-common:4.2.0.Final' 29 | } 30 | 31 | run { 32 | if ( project.hasProperty('args') ) { 33 | args project.args.split('\\s+') 34 | } 35 | } 36 | 37 | uploadArchives { 38 | repositories { 39 | flatDir { 40 | dirs 'repos' 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/ModelGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen; 2 | 3 | import com.google.gson.*; 4 | import com.github.dkharrat.nexusdata.modelgen.metamodel.*; 5 | import freemarker.template.*; 6 | import org.apache.commons.cli.*; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.*; 11 | import java.lang.reflect.Type; 12 | import java.nio.file.Files; 13 | 14 | public class ModelGenerator { 15 | 16 | static Logger LOG = LoggerFactory.getLogger(ModelGenerator.class); 17 | 18 | void generateModels(String modelPath, File outputDir) throws IOException { 19 | LOG.info("Setting up model generator"); 20 | 21 | try { 22 | freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_SLF4J); 23 | } catch (Exception e) { 24 | LOG.warn("Could not set logging for freemarker"); 25 | } 26 | Configuration cfg = new Configuration(); 27 | cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER); 28 | cfg.setClassForTemplateLoading(this.getClass(), "/templates"); 29 | cfg.setDefaultEncoding("UTF-8"); 30 | cfg.setWhitespaceStripping(true); 31 | cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); 32 | cfg.setIncompatibleImprovements(new Version(2, 3, 20)); 33 | 34 | LOG.info("Parsing model file '{}'", modelPath); 35 | Model model = parseFile(modelPath); 36 | 37 | LOG.info("Generating class files for '{}' model (version {})", model.getName(), model.getVersion()); 38 | File packageDir = getPackageDir(outputDir, model.getPackageName()); 39 | packageDir.mkdirs(); 40 | 41 | Template generatedModelTemplate = cfg.getTemplate("generated_model.ftl"); 42 | Template userModelTemplate = cfg.getTemplate("user_model.ftl"); 43 | 44 | for (Entity entity : model.getEntities()) { 45 | SimpleHash root = new SimpleHash(); 46 | root.put("entity", entity); 47 | root.put("packageName", model.getPackageName()); 48 | 49 | String userModelFileName = entity.getName() + ".java"; 50 | String genModelFileName = "_" + userModelFileName; 51 | 52 | try { 53 | File userModelFile = new File(packageDir, userModelFileName); 54 | if (!userModelFile.exists()) { 55 | LOG.info("Generating class {}", userModelFileName); 56 | Writer userModelOut = new FileWriter(userModelFile); 57 | userModelTemplate.process(root, userModelOut); 58 | } 59 | 60 | LOG.info("Generating class {}", genModelFileName); 61 | Writer genModelOut = new FileWriter(new File(packageDir, genModelFileName)); 62 | generatedModelTemplate.process(root, genModelOut); 63 | } catch (TemplateException ex) { 64 | throw new RuntimeException("Could not generate class files", ex); 65 | } 66 | } 67 | } 68 | 69 | Model parseFile(String filePath) throws IOException { 70 | 71 | File file = new File(filePath); 72 | byte[] bytes = Files.readAllBytes(file.toPath()); 73 | String json = new String(bytes, "UTF-8"); 74 | 75 | Gson gson = new GsonBuilder() 76 | .registerTypeAdapter(Entity.class, new EntityDeserializer()) 77 | .create(); 78 | 79 | ModelWrapper rootModel = gson.fromJson(json, ModelWrapper.class); 80 | 81 | // later on, when we have multiple version formats, we'll need to parse 82 | // model based on metaVersion 83 | return gson.fromJson(rootModel.model, Model.class); 84 | } 85 | 86 | static class EntityDeserializer implements JsonDeserializer { 87 | 88 | Gson gson = new Gson(); 89 | 90 | @Override 91 | public Entity deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { 92 | 93 | final Entity entity = gson.fromJson(json, typeOfT); 94 | for (Attribute attr : entity.getAttributes()) { 95 | attr.setEntity(entity); 96 | } 97 | return entity; 98 | } 99 | } 100 | 101 | File getPackageDir(File rootPath, String packageName) { 102 | String packageDir = packageName.replaceAll("\\.", File.separator); 103 | return new File(rootPath, packageDir); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/Startup.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen; 2 | 3 | import org.apache.commons.cli.*; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Properties; 9 | 10 | public class Startup { 11 | public static void main(String[] args) throws IOException { 12 | 13 | CommandLine line = parseCommandLineOptions(args); 14 | if (line != null) { 15 | String modelPath = line.getOptionValue(Cli.file.getOpt()); 16 | File outputPath = new File(line.getOptionValue(Cli.output.getOpt(), "./out")); 17 | 18 | new ModelGenerator().generateModels(modelPath, outputPath); 19 | } 20 | } 21 | 22 | private static class Cli { 23 | private static Option help = OptionBuilder 24 | .hasArgs(0) 25 | .withLongOpt("help") 26 | .withDescription("print this message") 27 | .isRequired(false) 28 | .create("h"); 29 | 30 | private static Option version = OptionBuilder 31 | .hasArgs(0) 32 | .withLongOpt("version") 33 | .withDescription("version number of modelgen") 34 | .isRequired(false) 35 | .create("v"); 36 | 37 | private static Option file = OptionBuilder 38 | .hasArg() 39 | .withLongOpt("file") 40 | .withDescription("the model file") 41 | .isRequired(true) 42 | .create("f"); 43 | 44 | private static Option output = OptionBuilder 45 | .hasArg() 46 | .withArgName("output") 47 | .withDescription("the output directory") 48 | .isRequired(false) 49 | .create("O"); 50 | 51 | static Options getMainOptions() { 52 | Options options = new Options(); 53 | options.addOption(Cli.file); 54 | options.addOption(Cli.output); 55 | options.addOption(Cli.version); 56 | options.addOption(Cli.help); 57 | return options; 58 | } 59 | 60 | static Options getHelpOptions() { 61 | Options options = new Options(); 62 | options.addOption(Cli.help); 63 | options.addOption(Cli.version); 64 | return options; 65 | } 66 | } 67 | 68 | private static CommandLine parseCommandLineOptions(String[] args) { 69 | 70 | Options helpOptions = Cli.getHelpOptions(); 71 | Options mainOptions = Cli.getMainOptions(); 72 | 73 | try { 74 | CommandLine cmdLine = new BasicParser().parse(helpOptions, args); 75 | if (cmdLine.hasOption(Cli.help.getOpt())) { 76 | printHelpMessage(mainOptions); 77 | return null; 78 | } else if (cmdLine.hasOption(Cli.version.getOpt())) { 79 | printVersion(); 80 | return null; 81 | } 82 | } catch( ParseException exp ) { 83 | // ignore 84 | } 85 | 86 | try { 87 | CommandLine cmdLine = new BasicParser().parse(mainOptions, args); 88 | return cmdLine; 89 | } catch( ParseException exp ) { 90 | System.err.println("Error parsing command-line: " + exp.getMessage()); 91 | return null; 92 | } 93 | } 94 | 95 | private static void printHelpMessage(Options mainOptions) { 96 | HelpFormatter formatter = new HelpFormatter(); 97 | formatter.printHelp("java modelgen.jar", mainOptions); 98 | } 99 | 100 | private static void printVersion() { 101 | System.out.println("modelgen version " + getVersion()); 102 | } 103 | 104 | private static String getVersion() { 105 | InputStream stream = Startup.class.getResourceAsStream("/version.properties"); 106 | if (stream == null) { 107 | return ""; 108 | } 109 | 110 | Properties props = new Properties(); 111 | try { 112 | props.load(stream); 113 | stream.close(); 114 | return (String) props.get("version"); 115 | } catch (IOException e) { 116 | return ""; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/Attribute.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class Attribute extends Property { 9 | 10 | private static final Map typeToJavaType = new HashMap<>(); 11 | static 12 | { 13 | typeToJavaType.put("String", "String"); 14 | typeToJavaType.put("Int", "Integer"); 15 | typeToJavaType.put("Long", "Long"); 16 | typeToJavaType.put("Bool", "Boolean"); 17 | typeToJavaType.put("Date", "Date"); 18 | typeToJavaType.put("Float", "Float"); 19 | typeToJavaType.put("Double", "Double"); 20 | } 21 | 22 | private static final Map typeToPrimType = new HashMap<>(); 23 | static 24 | { 25 | typeToPrimType.put("String", "String"); 26 | typeToPrimType.put("Int", "int"); 27 | typeToPrimType.put("Long", "long"); 28 | typeToPrimType.put("Bool", "boolean"); 29 | typeToPrimType.put("Date", "Date"); 30 | typeToPrimType.put("Float", "float"); 31 | typeToPrimType.put("Double", "double"); 32 | } 33 | 34 | private Entity entity; 35 | private String type; 36 | @SerializedName("default") private String defaultValue; 37 | 38 | // use to set parent entity when object is de-serialized 39 | public void setEntity(Entity entity) { 40 | this.entity = entity; 41 | } 42 | 43 | @Override 44 | public String getJavaType() { 45 | return getJavaType(true); 46 | } 47 | 48 | public String getJavaTypeForParam() { 49 | return getJavaType(!required); 50 | } 51 | 52 | private String getJavaType(boolean useObjectType) { 53 | String javaType = useObjectType ? typeToJavaType.get(type): typeToPrimType.get(type); 54 | if (javaType == null) { 55 | if (isEnumProperty(type)) { 56 | javaType = type; 57 | } 58 | } 59 | if (javaType == null) { 60 | throw new RuntimeException("Unknown type '" + type + "' for attribute '" + name + "'"); 61 | } 62 | return javaType; 63 | } 64 | 65 | private boolean isEnumProperty(String name) { 66 | for (EnumProperty enumProp : entity.getEnums()) { 67 | if (enumProp.getName().equals(name)) { 68 | return true; 69 | } 70 | } 71 | 72 | return false; 73 | } 74 | 75 | public String getMethodNameForGetter() { 76 | if (type.equals("Bool")) { 77 | if (name.substring(0, 2).equalsIgnoreCase("is")) { 78 | return name; 79 | } else { 80 | return "is" + getCapitalizedName(); 81 | } 82 | } else { 83 | return super.getMethodNameForGetter(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/Entity.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Entity { 9 | @SerializedName("extends") String baseClass = "ManagedObject"; 10 | String name; 11 | List attributes; 12 | List relationships; 13 | List enums; 14 | 15 | public Entity() { 16 | attributes = new ArrayList<>(); 17 | relationships = new ArrayList<>(); 18 | enums = new ArrayList<>(); 19 | } 20 | 21 | public String getBaseClass() { 22 | return baseClass; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public List getAttributes() { 30 | return attributes; 31 | } 32 | 33 | public List getRelationships() { 34 | return relationships; 35 | } 36 | 37 | public List getEnums() { 38 | return enums; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/EnumProperty.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | import com.google.common.base.CaseFormat; 4 | 5 | import java.util.List; 6 | 7 | public class EnumProperty extends Property { 8 | 9 | private List values; 10 | 11 | String getJavaType() { 12 | return name; 13 | } 14 | 15 | public List getValues() { 16 | return values; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/Model.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | import java.util.List; 4 | 5 | public class Model { 6 | String name; 7 | Integer version; 8 | String packageName; 9 | List entities; 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | public Integer getVersion() { 16 | return version; 17 | } 18 | 19 | public String getPackageName() { 20 | return packageName; 21 | } 22 | 23 | public List getEntities() { 24 | return entities; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/ModelWrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | import com.google.gson.JsonElement; 4 | 5 | public class ModelWrapper { 6 | public Integer metaVersion; 7 | public JsonElement model; // parsed separately so that metaVersion can be used 8 | } 9 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/Property.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | import com.google.common.base.CaseFormat; 4 | import org.modeshape.common.text.Inflector; 5 | 6 | public abstract class Property { 7 | 8 | String name; 9 | boolean required = false; 10 | boolean hasGetter = true; 11 | boolean hasSetter = true; 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | abstract String getJavaType(); 18 | 19 | static protected String capitalizedFirstChar(String str) { 20 | return str.substring(0, 1).toUpperCase() + str.substring(1); 21 | } 22 | 23 | public String getCapitalizedName() { 24 | if (name == null || name.isEmpty()) { 25 | throw new RuntimeException("Invalid attribute name"); 26 | } 27 | return capitalizedFirstChar(name); 28 | } 29 | 30 | public String getNameAsConstant() { 31 | return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, name); 32 | } 33 | 34 | public boolean isHasGetter() { 35 | return hasGetter; 36 | } 37 | 38 | public boolean isHasSetter() { 39 | return hasSetter; 40 | } 41 | 42 | public String getSingularName() { 43 | return Inflector.getInstance().singularize(name); 44 | } 45 | 46 | public String getMethodNameForGetter() { 47 | return "get" + getCapitalizedName(); 48 | } 49 | 50 | public String getMethodNameForSetter() { 51 | return "set" + getCapitalizedName(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modelgen/src/main/java/com/github/dkharrat/nexusdata/modelgen/metamodel/Relationship.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.modelgen.metamodel; 2 | 3 | public class Relationship extends Property { 4 | String destinationEntity; 5 | boolean toMany = false; 6 | 7 | public String getJavaType() { 8 | if (toMany) { 9 | return "Set<" + destinationEntity + ">"; 10 | } else { 11 | return destinationEntity; 12 | } 13 | } 14 | 15 | public boolean isToMany() { 16 | return toMany; 17 | } 18 | 19 | public String getDestinationEntity() { 20 | return destinationEntity; 21 | } 22 | 23 | public String getMethodNameForAddingToCollection() { 24 | return "add" + capitalizedFirstChar(getSingularName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modelgen/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /modelgen/src/main/resources/templates/generated_model.ftl: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package ${packageName}; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _${entity.name} extends ${entity.baseClass} { 10 | 11 | public interface Property { 12 | <#list entity.attributes as attribute> 13 | String ${attribute.getNameAsConstant()} = "${attribute.name}"; 14 | 15 | <#list entity.relationships as relationship> 16 | String ${relationship.getNameAsConstant()} = "${relationship.name}"; 17 | 18 | } 19 | 20 | <#list entity.enums as enum> 21 | public enum ${enum.name} { 22 | <#list enum.values as enumValue> 23 | ${enumValue}, 24 | 25 | } 26 | 27 | 28 | <#list entity.attributes as attribute> 29 | <#if attribute.hasGetter> 30 | public ${attribute.getJavaTypeForParam()} ${attribute.getMethodNameForGetter()}() { 31 | return (${attribute.getJavaType()})getValue(Property.${attribute.getNameAsConstant()}); 32 | } 33 | 34 | 35 | <#if attribute.hasSetter> 36 | public void ${attribute.getMethodNameForSetter()}(${attribute.getJavaTypeForParam()} ${attribute.name}) { 37 | setValue(Property.${attribute.getNameAsConstant()}, ${attribute.name}); 38 | } 39 | 40 | 41 | 42 | 43 | <#list entity.relationships as relationship> 44 | <#if relationship.hasGetter> 45 | <#if relationship.toMany> 46 | @SuppressWarnings("unchecked") 47 | 48 | public ${relationship.getJavaType()} ${relationship.getMethodNameForGetter()}() { 49 | return (${relationship.getJavaType()})getValue(Property.${relationship.getNameAsConstant()}); 50 | } 51 | 52 | 53 | <#if relationship.hasSetter> 54 | public void ${relationship.getMethodNameForSetter()}(${relationship.getJavaType()} ${relationship.name}) { 55 | setValue(Property.${relationship.getNameAsConstant()}, ${relationship.name}); 56 | } 57 | 58 | <#if relationship.toMany> 59 | public void ${relationship.getMethodNameForAddingToCollection()}(${relationship.destinationEntity} ${relationship.getSingularName()}) { 60 | ${relationship.getMethodNameForGetter()}().add(${relationship.getSingularName()}); 61 | } 62 | 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /modelgen/src/main/resources/templates/user_model.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | public class ${entity.name} extends _${entity.name} { 4 | 5 | public ${entity.name}() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /modelgen/src/main/resources/version.properties: -------------------------------------------------------------------------------- 1 | version=0.1.0 -------------------------------------------------------------------------------- /nexusdata/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.0.0' 7 | } 8 | } 9 | 10 | apply plugin: 'com.android.library' 11 | apply plugin: 'maven' 12 | apply plugin: 'signing' 13 | 14 | repositories { 15 | jcenter() 16 | } 17 | 18 | version = "0.2.1" 19 | group = "com.github.dkharrat.nexusdata" 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'org.slf4j:slf4j-api:1.7.6' 24 | compile 'com.google.code.gson:gson:2.2.4' 25 | androidTestCompile 'com.github.tony19:logback-android-core:1.1.1-3' 26 | androidTestCompile 'com.github.tony19:logback-android-classic:1.1.1-3' 27 | } 28 | 29 | android { 30 | compileSdkVersion 21 31 | buildToolsVersion '21.1.2' 32 | 33 | defaultConfig { 34 | minSdkVersion 10 35 | targetSdkVersion 21 36 | testApplicationId "com.github.dkharrat.nexusdata.test" 37 | testInstrumentationRunner "android.test.InstrumentationTestRunner" 38 | } 39 | buildTypes { 40 | release { 41 | minifyEnabled false 42 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 43 | } 44 | } 45 | 46 | libraryVariants.all { variant -> 47 | task("generate${variant.name}Javadoc", type: Javadoc) { 48 | description "Generates Javadoc for $variant.name." 49 | source = variant.javaCompile.source 50 | ext.androidJar = "${android.plugin.sdkHandler.sdkFolder}/platforms/${android.compileSdkVersion}/android.jar" 51 | classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar) 52 | exclude '**/R.html', '**/R.*.html', '**/BuildConfig.java' 53 | } 54 | } 55 | } 56 | 57 | task sourcesJar(type: Jar) { 58 | classifier = 'sources' 59 | from android.sourceSets.main.java.sourceFiles 60 | } 61 | 62 | artifacts { 63 | archives sourcesJar 64 | } 65 | 66 | if (project.hasProperty('signing')) { 67 | signing { 68 | required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } 69 | sign configurations.archives 70 | } 71 | } 72 | 73 | if (project.hasProperty('sonatypeUsername')) { 74 | uploadArchives { 75 | repositories.mavenDeployer { 76 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 77 | 78 | repository(url: sonatypeRepo) { 79 | authentication(userName: sonatypeUsername, password: sonatypePassword) 80 | } 81 | 82 | pom.project { 83 | name 'NexusData' 84 | packaging 'aar' 85 | description 'Core Data for Android' 86 | url 'https://github.com/dkharrat/NexusData' 87 | 88 | scm { 89 | url 'scm:git@github.com:dkharrat/NexusData.git' 90 | connection 'scm:git@github.com:dkharat/NexusData.git' 91 | developerConnection 'scm:git@github.com:dkharrat/NexusData.git' 92 | } 93 | 94 | licenses { 95 | license { 96 | name 'The Apache Software License, Version 2.0' 97 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 98 | distribution 'repo' 99 | } 100 | } 101 | 102 | developers { 103 | developer { 104 | id 'dkharrat' 105 | name 'Dia Kharrat' 106 | email 'dkharrat@gmail.com' 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /nexusdata/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in $ANDROID_SDK_PATH/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/assets/address.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaVersion": 1, 3 | "model": { 4 | "name": "AddressModel", 5 | "version": 1, 6 | "packageName": "com.github.dkharrat.nexusdata.test", 7 | "entities": [{ 8 | "name": "Address", 9 | "attributes": [{ 10 | "name": "streetName", 11 | "type": "String" 12 | }, { 13 | "name": "country", 14 | "type": "String", 15 | "required": true 16 | }] 17 | }] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/assets/company.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaVersion": 1, 3 | "model": { 4 | "name": "CompanyStructure", 5 | "version": 1, 6 | "packageName": "com.github.dkharrat.nexusdata.test", 7 | "includeModels": ["address.model.json"], 8 | "entities": [{ 9 | "name": "Company", 10 | "attributes": [{ 11 | "name": "name", 12 | "type": "String", 13 | "required": true 14 | }], 15 | "relationships": [{ 16 | "name": "employees", 17 | "destinationEntity": "Employee", 18 | "inverseName": "company", 19 | "toMany": true 20 | }, { 21 | "name": "directors", 22 | "destinationEntity": "Director", 23 | "inverseName": "company", 24 | "toMany": true 25 | }] 26 | }, { 27 | "name": "Person", 28 | "attributes": [{ 29 | "name": "id", 30 | "type": "Int", 31 | "required": true 32 | }, { 33 | "name": "firstName", 34 | "type": "String", 35 | "required": true 36 | }, { 37 | "name": "lastName", 38 | "type": "String" 39 | }, { 40 | "name": "heightInCm", 41 | "type": "Float" 42 | }, { 43 | "name": "dateOfBirth", 44 | "type": "Date", 45 | "required": true, 46 | "default": "1984-02-03T00:00:00" 47 | }] 48 | }, { 49 | "name": "Employee", 50 | "extends": "Person", 51 | "attributes": [{ 52 | "name": "hourlyWage", 53 | "type": "Double", 54 | "required": true, 55 | "default": 10.123 56 | }, { 57 | "name": "active", 58 | "type": "Bool", 59 | "required": true, 60 | "default": true 61 | }], 62 | "relationships": [{ 63 | "name": "company", 64 | "destinationEntity": "Company", 65 | "inverseName": "employees" 66 | }, { 67 | "name": "manager", 68 | "destinationEntity": "Employee", 69 | "inverseName": "directReports" 70 | }, { 71 | "name": "directReports", 72 | "destinationEntity": "Employee", 73 | "inverseName": "manager", 74 | "toMany": true 75 | }, { 76 | "name": "passport", 77 | "destinationEntity": "Passport", 78 | "inverseName": "employee" 79 | }, { 80 | "name": "address", 81 | "destinationEntity": "Address" 82 | }] 83 | }, { 84 | "name": "Director", 85 | "extends": "Person", 86 | "attributes": [{ 87 | "name": "active", 88 | "type": "Bool", 89 | "required": true, 90 | "default": true 91 | }], 92 | "relationships": [{ 93 | "name": "company", 94 | "destinationEntity": "Company", 95 | "inverseName": "directors" 96 | }] 97 | }, { 98 | "name": "Passport", 99 | "attributes": [{ 100 | "name": "number", 101 | "type": "String", 102 | "required": true 103 | }, { 104 | "name": "country", 105 | "type": "String", 106 | "required": true 107 | }, { 108 | "name": "issueDate", 109 | "type": "Date" 110 | }, { 111 | "name": "expirationDate", 112 | "type": "Date" 113 | }], 114 | "relationships": [{ 115 | "name": "employee", 116 | "destinationEntity": "Employee", 117 | "inverseName": "passport" 118 | }] 119 | }, { 120 | "name": "Contractor", 121 | "extends": "Employee", 122 | "attributes": [{ 123 | "name": "firmName", 124 | "type": "String", 125 | "required": true 126 | }] 127 | }] 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/assets/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | %logger{0} 7 | 8 | 9 | [%method] > %msg%n 10 | 11 | 12 | 13 | 14 | 15 | ${dest.dir}/app.log 16 | 17 | ${dest.dir}/app.%i.log 18 | 1 19 | 5 20 | 21 | 22 | 2MB 23 | 24 | true 25 | 26 | %d [%16.16t] %-5p %-45.45C{45} | %m%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Address.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | public class Address extends _Address { 4 | 5 | public Address() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Company.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import java.util.List; 4 | 5 | public class Company extends _Company { 6 | 7 | public Company() { 8 | } 9 | 10 | public void setEmployees(List employees) { 11 | setValue(Property.EMPLOYEES, employees); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Contractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | public class Contractor extends _Contractor { 4 | 5 | public Contractor() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Director.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | public class Director extends _Director { 4 | 5 | public Director() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Employee.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import java.util.Comparator; 4 | 5 | public class Employee extends _Employee { 6 | 7 | public Employee() { 8 | } 9 | 10 | public String getFullName() { 11 | return getFirstName() + " " + getLastName(); 12 | } 13 | 14 | public static Comparator getComparator() { 15 | return new Comparator() { 16 | @Override 17 | public int compare(Employee lhs, Employee rhs) { 18 | return lhs.getFullName().compareTo(rhs.getFullName()); 19 | } 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/ObjectContextWithInMemoryStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import com.github.dkharrat.nexusdata.core.PersistentStore; 4 | import com.github.dkharrat.nexusdata.store.InMemoryPersistentStore; 5 | 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | public class ObjectContextWithInMemoryStoreTest extends ObjectContextTest { 10 | 11 | @Override 12 | protected PersistentStore newPersistentStore() { 13 | return new InMemoryPersistentStore(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/ObjectContextWithSqlStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import com.github.dkharrat.nexusdata.core.PersistentStore; 4 | import com.github.dkharrat.nexusdata.store.AndroidSqlPersistentStore; 5 | 6 | public class ObjectContextWithSqlStoreTest extends ObjectContextTest { 7 | 8 | @Override 9 | protected PersistentStore newPersistentStore() { 10 | return new AndroidSqlPersistentStore(getContext(), getContext().getDatabasePath("test.db")); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/ObjectModelTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import java.util.ArrayList; 4 | 5 | import junit.framework.TestCase; 6 | import com.github.dkharrat.nexusdata.metamodel.ObjectModel; 7 | import com.github.dkharrat.nexusdata.metamodel.Entity; 8 | 9 | public class ObjectModelTest extends TestCase { 10 | 11 | ObjectModel model; 12 | 13 | @Override 14 | protected void setUp() throws Exception { 15 | super.setUp(); 16 | 17 | model = new ObjectModel(getClass().getResourceAsStream("/assets/company.model.json"), "/assets"); 18 | } 19 | 20 | @Override 21 | protected void tearDown() throws Exception { 22 | model = null; 23 | 24 | super.tearDown(); 25 | } 26 | 27 | public void testGetEntities() throws Throwable { 28 | assertEquals(7, model.getEntities().size()); 29 | 30 | ArrayList entityNames = new ArrayList(); 31 | for (Entity entity : model.getEntities()) { 32 | entityNames.add(entity.getName()); 33 | } 34 | assertTrue(entityNames.contains(Company.class.getSimpleName())); 35 | assertTrue(entityNames.contains(Person.class.getSimpleName())); 36 | assertTrue(entityNames.contains(Employee.class.getSimpleName())); 37 | assertTrue(entityNames.contains(Address.class.getSimpleName())); 38 | assertTrue(entityNames.contains(Passport.class.getSimpleName())); 39 | assertTrue(entityNames.contains(Contractor.class.getSimpleName())); 40 | assertTrue(entityNames.contains(Director.class.getSimpleName())); 41 | } 42 | 43 | public void testGetEntity() throws Throwable { 44 | assertEquals(Company.class.getSimpleName(), model.getEntity(Company.class).getName()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Passport.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | public class Passport extends _Passport { 4 | 5 | public Passport() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/PersistentStoreCoordinatorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import java.io.File; 4 | import android.test.AndroidTestCase; 5 | import com.github.dkharrat.nexusdata.core.ObjectContext; 6 | import com.github.dkharrat.nexusdata.core.PersistentStore; 7 | import com.github.dkharrat.nexusdata.core.PersistentStoreCoordinator; 8 | import com.github.dkharrat.nexusdata.metamodel.ObjectModel; 9 | import com.github.dkharrat.nexusdata.store.AndroidSqlPersistentStore; 10 | 11 | public class PersistentStoreCoordinatorTest extends AndroidTestCase { 12 | 13 | PersistentStoreCoordinator coordinator; 14 | ObjectContext context; 15 | 16 | @Override 17 | protected void setUp() throws Exception { 18 | super.setUp(); 19 | 20 | ObjectModel model = new ObjectModel(getClass().getResourceAsStream("/assets/company.model.json"), "/assets"); 21 | coordinator = new PersistentStoreCoordinator(model); 22 | PersistentStore persistentStore = new AndroidSqlPersistentStore(getContext(), getContext().getDatabasePath("test.db")); 23 | coordinator.addStore(persistentStore); 24 | context = new ObjectContext(coordinator); 25 | } 26 | 27 | @Override 28 | protected void tearDown() throws Exception { 29 | new File(coordinator.getPersistentStores().get(0).getLocation().toURI()).delete(); 30 | coordinator = null; 31 | context = null; 32 | 33 | super.tearDown(); 34 | } 35 | 36 | public void testGetUuidToObjectIDConversion() throws Throwable { 37 | Employee employee = context.newObject(Employee.class); 38 | employee.setId(123); 39 | employee.setFirstName("John"); 40 | employee.setLastName("Smith"); 41 | context.save(); 42 | 43 | assertEquals(employee.getID(), coordinator.objectIDFromUri(employee.getID().getUriRepresentation())); 44 | } 45 | 46 | public void testCantConvertTemporaryUuidToObjectID() throws Throwable { 47 | Employee employee = context.newObject(Employee.class); 48 | employee.setId(123); 49 | employee.setFirstName("John"); 50 | employee.setLastName("Smith"); 51 | 52 | boolean thrown = false; 53 | try { 54 | coordinator.objectIDFromUri(employee.getID().getUriRepresentation()); 55 | } catch (IllegalArgumentException e) { 56 | thrown = true; 57 | } 58 | 59 | assertTrue(thrown); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/Person.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | public class Person extends _Person { 4 | 5 | public Person() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/PredicateParserTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import junit.framework.TestCase; 4 | import com.github.dkharrat.nexusdata.predicate.ExpressionBuilder; 5 | import com.github.dkharrat.nexusdata.predicate.Predicate; 6 | import com.github.dkharrat.nexusdata.predicate.PredicateBuilder; 7 | 8 | public class PredicateParserTest extends TestCase { 9 | 10 | @Override 11 | protected void setUp() throws Exception { 12 | super.setUp(); 13 | } 14 | 15 | @Override 16 | protected void tearDown() throws Exception { 17 | super.tearDown(); 18 | } 19 | 20 | public void testWithEquality() throws Throwable { 21 | Predicate actual = PredicateBuilder.parse("1 == 256"); 22 | Predicate expected = ExpressionBuilder.constant(1).eq(256).getPredicate(); 23 | assertEquals(expected, actual); 24 | } 25 | 26 | public void testEqualityWithFieldName() throws Throwable { 27 | Predicate actual = PredicateBuilder.parse("pages == 362"); 28 | Predicate expected = ExpressionBuilder.field("pages").eq(362).getPredicate(); 29 | assertEquals(expected, actual); 30 | } 31 | 32 | public void testNullEqualityWithFieldName() throws Throwable { 33 | Predicate actual = PredicateBuilder.parse("pages == null"); 34 | Predicate expected = ExpressionBuilder.field("pages").isNull().getPredicate(); 35 | assertEquals(expected, actual); 36 | } 37 | 38 | public void testInequalityWithFieldName() throws Throwable { 39 | Predicate actual = PredicateBuilder.parse("pages != \"foo\""); 40 | Predicate expected = ExpressionBuilder.field("pages").notEq("foo").getPredicate(); 41 | assertEquals(expected, actual); 42 | } 43 | 44 | public void testWithString() throws Throwable { 45 | //TODO: this test hangs; fix 46 | Predicate actual = PredicateBuilder.parse("1 == \"2\""); 47 | Predicate expected = ExpressionBuilder.constant(1).eq("2").getPredicate(); 48 | assertEquals(expected, actual); 49 | } 50 | 51 | public void testComparison() throws Throwable { 52 | Predicate actual = PredicateBuilder.parse("10 > 5"); 53 | Predicate expected = ExpressionBuilder.constant(10).gt(5).getPredicate(); 54 | assertEquals(expected, actual); 55 | } 56 | 57 | public void testLogical() throws Throwable { 58 | Predicate actual = PredicateBuilder.parse("10 > 5 && 8 < 12"); 59 | Predicate expected = ExpressionBuilder.constant(10).gt(5).and(ExpressionBuilder.constant(8).lt(12)).getPredicate(); 60 | assertEquals(expected, actual); 61 | } 62 | 63 | public void testPrecedence1() throws Throwable { 64 | Predicate actual = PredicateBuilder.parse("10 > 5 || (8 == 8 && 8 < 6)"); 65 | Predicate expected = ExpressionBuilder.constant(10).gt(5) 66 | .or(ExpressionBuilder.constant(8).eq(8) 67 | .and(ExpressionBuilder.constant(8).lt(6))).getPredicate(); 68 | assertEquals(expected, actual); 69 | } 70 | 71 | public void testPrecedence2() throws Throwable { 72 | Predicate actual = PredicateBuilder.parse("(10 > 5 || 8 == 8) && 8 < 6"); 73 | Predicate expected = (ExpressionBuilder.constant(10).gt(5).or(ExpressionBuilder.constant(8).eq(8)).and(ExpressionBuilder.constant(8).lt(6))).getPredicate(); 74 | assertEquals(expected, actual); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/PredicatesTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.test; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import com.github.dkharrat.nexusdata.predicate.ExpressionBuilder; 6 | import com.github.dkharrat.nexusdata.predicate.Predicate; 7 | 8 | public class PredicatesTest extends TestCase { 9 | 10 | class Book { 11 | String title; 12 | String authorName; 13 | int pages; 14 | 15 | Book(String title, int pages) { 16 | this.title = title; 17 | this.pages = pages; 18 | } 19 | } 20 | 21 | @Override 22 | protected void setUp() throws Exception { 23 | super.setUp(); 24 | } 25 | 26 | @Override 27 | protected void tearDown() throws Exception { 28 | super.tearDown(); 29 | } 30 | 31 | public void testSimplePredicateTrue() throws Throwable { 32 | Predicate p = ExpressionBuilder.constant("1").eq("1").getPredicate(); 33 | assertTrue(p.evaluate(null)); 34 | } 35 | 36 | public void testPredicateWithFieldPathExpression() throws Throwable { 37 | 38 | Book book = new Book("Book one", 362); 39 | 40 | Predicate p = ExpressionBuilder.field("title").eq("Book one").getPredicate(); 41 | assertTrue(p.evaluate(book)); 42 | 43 | p = ExpressionBuilder.field("pages").eq(362).getPredicate(); 44 | assertTrue(p.evaluate(book)); 45 | 46 | p = ExpressionBuilder.field("pages").eq(363).getPredicate(); 47 | assertFalse(p.evaluate(book)); 48 | 49 | p = ExpressionBuilder.field("authorName").isNull().getPredicate(); 50 | assertTrue(p.evaluate(book)); 51 | } 52 | 53 | public void testSimplePredicateFalse() throws Throwable { 54 | Predicate p = ExpressionBuilder.constant("1").eq("2").getPredicate(); 55 | assertFalse(p.evaluate(null)); 56 | } 57 | 58 | public void testComparisonPredicateWithThis() throws Throwable { 59 | Book book1 = new Book("Book one", 362); 60 | Book book2 = new Book("Book two", 427); 61 | 62 | Predicate p = ExpressionBuilder.self().eq(book1).getPredicate(); 63 | assertTrue(p.evaluate(book1)); 64 | assertFalse(p.evaluate(book2)); 65 | } 66 | 67 | public void testComparisonPredicateTrue() throws Throwable { 68 | Predicate p = ExpressionBuilder.constant(10).gt(5).getPredicate(); 69 | assertTrue(p.evaluate(null)); 70 | } 71 | 72 | public void testComparisonPredicateFalse() throws Throwable { 73 | Predicate p = ExpressionBuilder.constant(10).gt(15).getPredicate(); 74 | assertFalse(p.evaluate(null)); 75 | } 76 | 77 | public void testCompoundPredicateTrue() throws Throwable { 78 | Predicate p = ExpressionBuilder.constant(10).gt(5).and(ExpressionBuilder.constant(8).lt(12)).getPredicate(); 79 | assertTrue(p.evaluate(null)); 80 | } 81 | 82 | public void testCompoundPredicateFalse() throws Throwable { 83 | Predicate p = ExpressionBuilder.constant(10).gt(5).and(ExpressionBuilder.constant(8).lt(6)).getPredicate(); 84 | assertFalse(p.evaluate(null)); 85 | } 86 | 87 | public void testComplexCompoundPredicateTrue() throws Throwable { 88 | // 10 > 5 || (8 != 9 && 8 < 6) 89 | Predicate p = ExpressionBuilder.constant(10).gt(5) 90 | .or(ExpressionBuilder.constant(8).notEq(9) 91 | .and(ExpressionBuilder.constant(8).lt(6))).getPredicate(); 92 | assertTrue(p.evaluate(null)); 93 | } 94 | 95 | public void testComplexCompoundPredicateFalse() throws Throwable { 96 | // (10 > 5 || 8 != 9) && 8 < 6 97 | Predicate p = (ExpressionBuilder.constant(10).gt(5).or(ExpressionBuilder.constant(8).notEq(9)).and(ExpressionBuilder.constant(8).lt(6))).getPredicate(); 98 | assertFalse(p.evaluate(null)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Address.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Address extends ManagedObject { 10 | 11 | public interface Property { 12 | final static String STREET_NAME = "streetName"; 13 | final static String COUNTRY = "country"; 14 | } 15 | 16 | 17 | public String getStreetName() { 18 | return (String)getValue(Property.STREET_NAME); 19 | } 20 | 21 | public void setStreetName(String streetName) { 22 | setValue(Property.STREET_NAME, streetName); 23 | } 24 | 25 | public String getCountry() { 26 | return (String)getValue(Property.COUNTRY); 27 | } 28 | 29 | public void setCountry(String country) { 30 | setValue(Property.COUNTRY, country); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Company.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Company extends ManagedObject { 10 | 11 | public interface Property { 12 | String NAME = "name"; 13 | String EMPLOYEES = "employees"; 14 | String DIRECTORS = "directors"; 15 | } 16 | 17 | 18 | public String getName() { 19 | return (String)getValue(Property.NAME); 20 | } 21 | 22 | public void setName(String name) { 23 | setValue(Property.NAME, name); 24 | } 25 | 26 | 27 | @SuppressWarnings("unchecked") 28 | public Set getEmployees() { 29 | return (Set)getValue(Property.EMPLOYEES); 30 | } 31 | 32 | public void setEmployees(Set employees) { 33 | setValue(Property.EMPLOYEES, employees); 34 | } 35 | 36 | public void addEmployee(Employee employee) { 37 | getEmployees().add(employee); 38 | } 39 | @SuppressWarnings("unchecked") 40 | public Set getDirectors() { 41 | return (Set)getValue(Property.DIRECTORS); 42 | } 43 | 44 | public void setDirectors(Set directors) { 45 | setValue(Property.DIRECTORS, directors); 46 | } 47 | 48 | public void addDirector(Director director) { 49 | getDirectors().add(director); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Contractor.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Contractor extends Employee { 10 | 11 | public interface Property { 12 | String FIRM_NAME = "firmName"; 13 | } 14 | 15 | 16 | public String getFirmName() { 17 | return (String)getValue(Property.FIRM_NAME); 18 | } 19 | 20 | public void setFirmName(String firmName) { 21 | setValue(Property.FIRM_NAME, firmName); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Director.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Director extends Person { 10 | 11 | public interface Property { 12 | String ACTIVE = "active"; 13 | String COMPANY = "company"; 14 | } 15 | 16 | 17 | public boolean isActive() { 18 | return (Boolean)getValue(Property.ACTIVE); 19 | } 20 | 21 | public void setActive(boolean active) { 22 | setValue(Property.ACTIVE, active); 23 | } 24 | 25 | 26 | public Company getCompany() { 27 | return (Company)getValue(Property.COMPANY); 28 | } 29 | 30 | public void setCompany(Company company) { 31 | setValue(Property.COMPANY, company); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Employee.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Employee extends Person { 10 | 11 | public interface Property { 12 | String HOURLY_WAGE = "hourlyWage"; 13 | String ACTIVE = "active"; 14 | String COMPANY = "company"; 15 | String MANAGER = "manager"; 16 | String DIRECT_REPORTS = "directReports"; 17 | String PASSPORT = "passport"; 18 | String ADDRESS = "address"; 19 | } 20 | 21 | 22 | public double getHourlyWage() { 23 | return (Double)getValue(Property.HOURLY_WAGE); 24 | } 25 | 26 | public void setHourlyWage(double hourlyWage) { 27 | setValue(Property.HOURLY_WAGE, hourlyWage); 28 | } 29 | 30 | public boolean isActive() { 31 | return (Boolean)getValue(Property.ACTIVE); 32 | } 33 | 34 | public void setActive(boolean active) { 35 | setValue(Property.ACTIVE, active); 36 | } 37 | 38 | 39 | public Company getCompany() { 40 | return (Company)getValue(Property.COMPANY); 41 | } 42 | 43 | public void setCompany(Company company) { 44 | setValue(Property.COMPANY, company); 45 | } 46 | 47 | public Employee getManager() { 48 | return (Employee)getValue(Property.MANAGER); 49 | } 50 | 51 | public void setManager(Employee manager) { 52 | setValue(Property.MANAGER, manager); 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | public Set getDirectReports() { 57 | return (Set)getValue(Property.DIRECT_REPORTS); 58 | } 59 | 60 | public void setDirectReports(Set directReports) { 61 | setValue(Property.DIRECT_REPORTS, directReports); 62 | } 63 | 64 | public void addDirectReport(Employee directReport) { 65 | getDirectReports().add(directReport); 66 | } 67 | public Passport getPassport() { 68 | return (Passport)getValue(Property.PASSPORT); 69 | } 70 | 71 | public void setPassport(Passport passport) { 72 | setValue(Property.PASSPORT, passport); 73 | } 74 | 75 | public Address getAddress() { 76 | return (Address)getValue(Property.ADDRESS); 77 | } 78 | 79 | public void setAddress(Address address) { 80 | setValue(Property.ADDRESS, address); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Passport.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Passport extends ManagedObject { 10 | 11 | public interface Property { 12 | String NUMBER = "number"; 13 | String COUNTRY = "country"; 14 | String ISSUE_DATE = "issueDate"; 15 | String EXPIRATION_DATE = "expirationDate"; 16 | String EMPLOYEE = "employee"; 17 | } 18 | 19 | 20 | public String getNumber() { 21 | return (String)getValue(Property.NUMBER); 22 | } 23 | 24 | public void setNumber(String number) { 25 | setValue(Property.NUMBER, number); 26 | } 27 | 28 | public String getCountry() { 29 | return (String)getValue(Property.COUNTRY); 30 | } 31 | 32 | public void setCountry(String country) { 33 | setValue(Property.COUNTRY, country); 34 | } 35 | 36 | public Date getIssueDate() { 37 | return (Date)getValue(Property.ISSUE_DATE); 38 | } 39 | 40 | public void setIssueDate(Date issueDate) { 41 | setValue(Property.ISSUE_DATE, issueDate); 42 | } 43 | 44 | public Date getExpirationDate() { 45 | return (Date)getValue(Property.EXPIRATION_DATE); 46 | } 47 | 48 | public void setExpirationDate(Date expirationDate) { 49 | setValue(Property.EXPIRATION_DATE, expirationDate); 50 | } 51 | 52 | 53 | public Employee getEmployee() { 54 | return (Employee)getValue(Property.EMPLOYEE); 55 | } 56 | 57 | public void setEmployee(Employee employee) { 58 | setValue(Property.EMPLOYEE, employee); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/java/com/github/dkharrat/nexusdata/test/_Person.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package com.github.dkharrat.nexusdata.test; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Person extends ManagedObject { 10 | 11 | public interface Property { 12 | String ID = "id"; 13 | String FIRST_NAME = "firstName"; 14 | String LAST_NAME = "lastName"; 15 | String HEIGHT_IN_CM = "heightInCm"; 16 | String DATE_OF_BIRTH = "dateOfBirth"; 17 | } 18 | 19 | 20 | public int getId() { 21 | return (Integer)getValue(Property.ID); 22 | } 23 | 24 | public void setId(int id) { 25 | setValue(Property.ID, id); 26 | } 27 | 28 | public String getFirstName() { 29 | return (String)getValue(Property.FIRST_NAME); 30 | } 31 | 32 | public void setFirstName(String firstName) { 33 | setValue(Property.FIRST_NAME, firstName); 34 | } 35 | 36 | public String getLastName() { 37 | return (String)getValue(Property.LAST_NAME); 38 | } 39 | 40 | public void setLastName(String lastName) { 41 | setValue(Property.LAST_NAME, lastName); 42 | } 43 | 44 | public Float getHeightInCm() { 45 | return (Float)getValue(Property.HEIGHT_IN_CM); 46 | } 47 | 48 | public void setHeightInCm(Float heightInCm) { 49 | setValue(Property.HEIGHT_IN_CM, heightInCm); 50 | } 51 | 52 | public Date getDateOfBirth() { 53 | return (Date)getValue(Property.DATE_OF_BIRTH); 54 | } 55 | 56 | public void setDateOfBirth(Date dateOfBirth) { 57 | setValue(Property.DATE_OF_BIRTH, dateOfBirth); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /nexusdata/src/androidTest/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/nexusdata/src/androidTest/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /nexusdata/src/androidTest/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/nexusdata/src/androidTest/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /nexusdata/src/androidTest/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/nexusdata/src/androidTest/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /nexusdata/src/androidTest/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/nexusdata/src/androidTest/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /nexusdata/src/androidTest/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Px-droid-testTest 5 | 6 | -------------------------------------------------------------------------------- /nexusdata/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/AtomicStore.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | import java.util.*; 6 | 7 | import com.github.dkharrat.nexusdata.metamodel.Property; 8 | import com.github.dkharrat.nexusdata.metamodel.Relationship; 9 | import com.github.dkharrat.nexusdata.utils.ObjectUtil; 10 | 11 | /** 12 | * An AtomicStore is a persistence store in which data is loaded and saved all at once. It is useful when the data set 13 | * is small enough to fit in memory and good performance is needed. 14 | */ 15 | public abstract class AtomicStore extends PersistentStore { 16 | 17 | private final Map idsToCacheNodes = new HashMap(); 18 | 19 | /** 20 | * Constructs a new Atomic store 21 | * 22 | * @param location the location in which to save the data persistence file 23 | */ 24 | public AtomicStore(URL location) { 25 | super(location); 26 | } 27 | 28 | public AtomicStore(File location) { 29 | super(location); 30 | } 31 | 32 | public abstract void load(); 33 | public abstract void save(); 34 | public abstract Object createReferenceObjectForManagedObject(ManagedObject object); 35 | 36 | @Override 37 | protected void loadMetadata() { 38 | setUuid(UUID.randomUUID()); 39 | } 40 | 41 | Set getCacheNodes() { 42 | return new HashSet(idsToCacheNodes.values()); 43 | } 44 | 45 | protected void addCacheNode(StoreCacheNode cacheNode) { 46 | idsToCacheNodes.put(cacheNode.getID(), cacheNode); 47 | } 48 | 49 | protected void removeCacheNode(StoreCacheNode cacheNode) { 50 | idsToCacheNodes.remove(cacheNode.getID()); 51 | } 52 | 53 | protected void updateCacheNode(StoreCacheNode cacheNode, ManagedObject object) { 54 | for(Property property : object.getEntity().getProperties()) { 55 | Object value = object.getValue(property.getName()); 56 | if (property.isRelationship()) { 57 | Relationship relationship = (Relationship) property; 58 | if (relationship.isToOne()) { 59 | ManagedObject relatedObject = (ManagedObject) value; 60 | if (relatedObject != null) { 61 | cacheNode.setProperty(relationship.getName(), relatedObject.getID()); 62 | } 63 | } else { 64 | FaultingSet relatedObjects = (FaultingSet) value; 65 | cacheNode.setProperty(relationship.getName(), relatedObjects.getObjectIDs()); 66 | } 67 | } else { 68 | cacheNode.setProperty(property.getName(), value); 69 | } 70 | } 71 | } 72 | 73 | StoreCacheNode createCacheNode(ManagedObject object) { 74 | ObjectID id = object.getID(); 75 | StoreCacheNode cacheNode = new StoreCacheNode(id); 76 | updateCacheNode(cacheNode, object); 77 | 78 | return cacheNode; 79 | } 80 | 81 | private StoreCacheNode getCacheNode(ObjectID objectID) { 82 | return idsToCacheNodes.get(objectID); 83 | } 84 | 85 | @Override 86 | List getPermanentIDsForObjects(List objects) { 87 | List objectIDs = new ArrayList(); 88 | for (ManagedObject object : objects) { 89 | ObjectID id; 90 | 91 | Object refObject = createReferenceObjectForManagedObject(object); 92 | id = createObjectID(object.getEntity(), refObject); 93 | 94 | objectIDs.add(id); 95 | } 96 | 97 | return objectIDs; 98 | } 99 | 100 | @Override 101 | StoreCacheNode getObjectValues(ObjectID objectID, ObjectContext context) { 102 | return getCacheNode(objectID); 103 | } 104 | 105 | @Override 106 | ObjectID getToOneRelationshipValue( 107 | ObjectID objectID, 108 | Relationship relationship, 109 | ObjectContext context) { 110 | 111 | StoreCacheNode cacheNode = getCacheNode(objectID); 112 | 113 | return (ObjectID) cacheNode.getProperty(relationship.getName()); 114 | } 115 | 116 | @Override 117 | Set getToManyRelationshipValue( 118 | ObjectID objectID, 119 | Relationship relationship, 120 | ObjectContext context) { 121 | 122 | StoreCacheNode cacheNode = getCacheNode(objectID); 123 | @SuppressWarnings("unchecked") 124 | Set relatedObjectIDs = (Set) cacheNode.getProperty(relationship.getName()); 125 | 126 | return relatedObjectIDs; 127 | } 128 | 129 | private void sort(final List list, final List sortDescriptors) { 130 | Collections.sort(list, new Comparator() { 131 | @SuppressWarnings("unchecked") 132 | @Override 133 | public int compare(T lhs, T rhs) { 134 | int result = 0; 135 | for (SortDescriptor sortDesc : sortDescriptors) { 136 | Object lhsValue = lhs.getValue(sortDesc.getAttributeName()); 137 | Object rhsValue = rhs.getValue(sortDesc.getAttributeName()); 138 | 139 | if (lhsValue == null && rhsValue == null) { 140 | return 0; 141 | } else if (lhsValue == null || rhsValue == null) { 142 | return -1; 143 | } else { 144 | @SuppressWarnings("rawtypes") 145 | Comparable lhsComparable = ObjectUtil.toComparable(lhsValue); 146 | @SuppressWarnings("rawtypes") 147 | Comparable rhsComparable = ObjectUtil.toComparable(rhsValue); 148 | result = lhsComparable.compareTo(rhsComparable); 149 | 150 | if (result == 0) { 151 | continue; 152 | } 153 | 154 | result = sortDesc.isAscending() ? result : -result; 155 | } 156 | } 157 | return result; 158 | } 159 | }); 160 | } 161 | 162 | @Override 163 | List executeFetchRequest(final FetchRequest request, final ObjectContext context) { 164 | List results = new ArrayList(); 165 | 166 | for (StoreCacheNode cacheNode : getCacheNodes()) { 167 | ObjectID objID = cacheNode.getID(); 168 | if (request.getEntity().getType().isAssignableFrom(objID.getType())) { 169 | @SuppressWarnings("unchecked") 170 | T obj = (T)context.getExistingObject(objID); 171 | 172 | if (request.getPredicate() == null || request.getPredicate().evaluate(obj)) { 173 | results.add(obj); 174 | } 175 | } 176 | } 177 | 178 | if (request.hasSortDescriptors()) { 179 | sort(results, request.getSortDescriptors()); 180 | } 181 | 182 | if (request.getLimit() < Integer.MAX_VALUE && request.getLimit() < results.size()) { 183 | results = results.subList(0, request.getLimit()); 184 | } 185 | 186 | return results; 187 | } 188 | 189 | @Override 190 | void executeSaveRequest(SaveChangesRequest request, ObjectContext context) { 191 | for (ManagedObject object : request.getChanges().getInsertedObjects()) { 192 | StoreCacheNode cacheNode = createCacheNode(object); 193 | addCacheNode(cacheNode); 194 | } 195 | 196 | for (ManagedObject object : request.getChanges().getDeletedObjects()) { 197 | if (!object.isInserted()) { 198 | StoreCacheNode cacheNode = getObjectValues(object.getID(), context); 199 | removeCacheNode(cacheNode); 200 | object.setManagedObjectContext(null); 201 | } 202 | } 203 | 204 | for (ManagedObject object : request.getChanges().getUpdatedObjects()) { 205 | StoreCacheNode cacheNode = getObjectValues(object.getID(), context); 206 | updateCacheNode(cacheNode, object); 207 | } 208 | 209 | save(); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/ChangedObjectsSet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * Represents a set of objects that have changed in an {@link ObjectContext}. It is used by events sent out from 8 | * {@link ObjectContextNotifier}, persistence store save requests, etc. 9 | */ 10 | public class ChangedObjectsSet { 11 | 12 | private final Set insertedObjects; 13 | private final Set updatedObjects; 14 | private final Set deletedObjects; 15 | 16 | ChangedObjectsSet() { 17 | this(new HashSet(), new HashSet(), new HashSet()); 18 | } 19 | 20 | ChangedObjectsSet(ChangedObjectsSet other) { 21 | this(new HashSet(other.getInsertedObjects()), 22 | new HashSet(other.getUpdatedObjects()), 23 | new HashSet(other.getDeletedObjects())); 24 | } 25 | 26 | ChangedObjectsSet(Set insertedObjects, Set updatedObjects, Set deletedObjects) { 27 | this.insertedObjects = insertedObjects; 28 | this.updatedObjects = updatedObjects; 29 | this.deletedObjects = deletedObjects; 30 | } 31 | 32 | void objectInserted(ManagedObject object) { 33 | if (!object.isDeleted()) { 34 | insertedObjects.add(object); 35 | } 36 | deletedObjects.remove(object); 37 | } 38 | 39 | void objectUpdated(ManagedObject object) { 40 | if (!insertedObjects.contains(object) && !deletedObjects.contains(object)) { 41 | updatedObjects.add(object); 42 | } 43 | } 44 | 45 | void objectDeleted(ManagedObject object, boolean trackDeletionEvenIfNew) { 46 | insertedObjects.remove(object); 47 | updatedObjects.remove(object); 48 | if (!object.isInserted() || trackDeletionEvenIfNew) { 49 | deletedObjects.add(object); 50 | } 51 | } 52 | 53 | void clear() { 54 | insertedObjects.clear(); 55 | deletedObjects.clear(); 56 | updatedObjects.clear(); 57 | } 58 | 59 | /** 60 | * Returns the set of objects that have been inserted into the ObjectContext. 61 | * 62 | * @return the set of objects that have been inserted 63 | */ 64 | public Set getInsertedObjects() { 65 | return insertedObjects; 66 | } 67 | 68 | /** 69 | * Returns the set of objects that have been updated (have property changes). 70 | * 71 | * @return the set of objects that have been updated 72 | */ 73 | public Set getUpdatedObjects() { 74 | return updatedObjects; 75 | } 76 | 77 | /** 78 | * Returns the set of objects that have been deleted from the ObjectContext. 79 | * 80 | * @return the set of objects that have been deleted 81 | */ 82 | public Set getDeletedObjects() { 83 | return deletedObjects; 84 | } 85 | 86 | /** 87 | * Returns true if the specified object is marked as inserted by this change set 88 | * 89 | * @return true if the specified object is marked as inserted by this change set 90 | */ 91 | public boolean isInserted(ManagedObject object) { 92 | return insertedObjects.contains(object); 93 | } 94 | 95 | /** 96 | * Returns true if the specified object is marked as updated by this change set 97 | * 98 | * @return true if the specified object is marked as updated by this change set 99 | */ 100 | public boolean isUpdated(ManagedObject object) { 101 | return updatedObjects.contains(object); 102 | } 103 | 104 | /** 105 | * Returns true if the specified object is marked as deleted by this change set 106 | * 107 | * @return true if the specified object is marked as deleted by this change set 108 | */ 109 | public boolean isDeleted(ManagedObject object) { 110 | return deletedObjects.contains(object); 111 | } 112 | 113 | /** 114 | * Returns true if this change set is non-empty (i.e. changes exist) 115 | * 116 | * @return true if this change set is non-empty 117 | */ 118 | public boolean hasChanges() { 119 | return !insertedObjects.isEmpty() || 120 | !updatedObjects.isEmpty() || 121 | !deletedObjects.isEmpty(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/FaultingSet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.LinkedHashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | import com.github.dkharrat.nexusdata.metamodel.Relationship; 11 | 12 | class FaultingSet implements Set { 13 | 14 | private final ManagedObject parent; 15 | private Set backingSet = new LinkedHashSet(); 16 | private boolean isFault = false; 17 | private final Relationship relationship; 18 | 19 | FaultingSet(ManagedObject parent, Relationship relationship, Collection objects) { 20 | this.parent = parent; 21 | this.relationship = relationship; 22 | isFault = !this.parent.isInserted() && objects == null; 23 | if (objects != null) { 24 | setObjects(objects); 25 | } 26 | } 27 | 28 | private ObjectContext getContext() { 29 | return parent.getObjectContext(); 30 | } 31 | 32 | void fulfillFaultIfNecessary() { 33 | if (isFault()) { 34 | getContext().faultInObjectRelationship(parent, relationship); 35 | isFault = false; 36 | } 37 | } 38 | 39 | public boolean isFault() { 40 | return isFault; 41 | } 42 | 43 | public void refresh() { 44 | isFault = !parent.isInserted(); 45 | backingSet = new LinkedHashSet(); 46 | } 47 | 48 | void setObjects(Collection objects) { 49 | backingSet = new LinkedHashSet(objects); 50 | } 51 | 52 | /** 53 | * Clones this set such that it's compatible with a different context 54 | * @param otherContext 55 | */ 56 | FaultingSet getObjectsInContext(ObjectContext otherContext) { 57 | if (!getContext().isCompatibleWithContext(otherContext)) { 58 | throw new RuntimeException("Contexts not compatible. Ensure that they share the same persistence store."); 59 | } 60 | 61 | List relatedObjects = null; 62 | if (!isFault()) { 63 | relatedObjects = new ArrayList(); 64 | for (E relatedObject : this) { 65 | relatedObjects.add((E) otherContext.objectWithID(relatedObject.getID())); 66 | } 67 | } 68 | 69 | FaultingSet relatedObjectsSet = new FaultingSet(otherContext.objectWithID(parent.getID()), relationship, relatedObjects); 70 | return relatedObjectsSet; 71 | } 72 | 73 | private void setInverseRelationshipValue(ManagedObject relatedObject) { 74 | relatedObject.setValueDirectly(relationship.getInverse(), parent); 75 | parent.notifyManagedObjectContextOfChange(); 76 | } 77 | 78 | private void clearInverseRelationshipValue(ManagedObject relatedObject) { 79 | relatedObject.setValueDirectly(relationship.getInverse(), null); 80 | parent.notifyManagedObjectContextOfChange(); 81 | } 82 | 83 | @Override 84 | public boolean add(E object) { 85 | if (object.getObjectContext() != null && object.getObjectContext() != getContext()) { 86 | throw new UnsupportedOperationException("Cannot add an object that belongs to another context"); 87 | } 88 | 89 | if (!object.isInserted() && object.getObjectContext() == null) { 90 | throw new UnsupportedOperationException("Cannot add an object that is not registered with a context"); 91 | } 92 | 93 | fulfillFaultIfNecessary(); 94 | 95 | setInverseRelationshipValue(object); 96 | 97 | if (object.isInserted()) { 98 | getContext().insert(object); 99 | } 100 | object.notifyManagedObjectContextOfChange(); 101 | 102 | return backingSet.add(object); 103 | } 104 | 105 | @Override 106 | public boolean addAll(Collection objects) { 107 | boolean changed = false; 108 | for (E object : objects) { 109 | changed = add(object) || changed; 110 | } 111 | return changed; 112 | } 113 | 114 | @Override 115 | public void clear() { 116 | fulfillFaultIfNecessary(); 117 | 118 | for (E object : backingSet) { 119 | clearInverseRelationshipValue(object); 120 | } 121 | 122 | backingSet.clear(); 123 | } 124 | 125 | @Override 126 | public boolean contains(Object object) { 127 | fulfillFaultIfNecessary(); 128 | return backingSet.contains(object); 129 | } 130 | 131 | @Override 132 | public boolean containsAll(Collection objects) { 133 | fulfillFaultIfNecessary(); 134 | return backingSet.containsAll(objects); 135 | } 136 | 137 | @Override 138 | public boolean isEmpty() { 139 | fulfillFaultIfNecessary(); 140 | return backingSet.isEmpty(); 141 | } 142 | 143 | @Override 144 | public Iterator iterator() { 145 | fulfillFaultIfNecessary(); 146 | return backingSet.iterator(); 147 | } 148 | 149 | @Override 150 | public boolean remove(Object o) { 151 | fulfillFaultIfNecessary(); 152 | 153 | if (o instanceof ManagedObject) { 154 | ManagedObject object = (ManagedObject)o; 155 | clearInverseRelationshipValue(object); 156 | if (object.isInserted()) { 157 | getContext().delete(object); 158 | } else { 159 | object.notifyManagedObjectContextOfChange(); 160 | } 161 | } 162 | 163 | return backingSet.remove(o); 164 | } 165 | 166 | @Override 167 | public boolean removeAll(Collection objects) { 168 | boolean changed = false; 169 | for (Object object : objects) { 170 | changed = remove(object) || changed; 171 | } 172 | return changed; 173 | } 174 | 175 | @Override 176 | public boolean retainAll(Collection objects) { 177 | throw new UnsupportedOperationException("Not yet implemented"); 178 | } 179 | 180 | @Override 181 | public int size() { 182 | fulfillFaultIfNecessary(); 183 | return backingSet.size(); 184 | 185 | } 186 | 187 | @Override 188 | public Object[] toArray() { 189 | fulfillFaultIfNecessary(); 190 | return backingSet.toArray(); 191 | } 192 | 193 | @Override 194 | public T[] toArray(T[] array) { 195 | fulfillFaultIfNecessary(); 196 | return backingSet.toArray(array); 197 | } 198 | 199 | @Override 200 | public String toString() { 201 | if (isFault()) { 202 | return "Relationship '"+ relationship.getName()+"' fault on object "+ parent.toObjectReferenceString(); 203 | } else { 204 | return "Relationship '"+ relationship.getName()+"' on object "+ parent.toObjectReferenceString()+";\n"+ 205 | " values: "+ backingSet.toString(); 206 | } 207 | } 208 | 209 | public Set getObjectIDs() { 210 | fulfillFaultIfNecessary(); 211 | 212 | Set objectIDs = new LinkedHashSet(); 213 | for (ManagedObject object : backingSet) { 214 | objectIDs.add(object.getID()); 215 | } 216 | return objectIDs; 217 | } 218 | 219 | @Override 220 | public int hashCode() { 221 | final int prime = 31; 222 | int result = 1; 223 | result = prime * result + ((backingSet == null) ? 0 : backingSet.hashCode()); 224 | result = prime * result + ((parent == null) ? 0 : parent.hashCode()); 225 | result = prime * result + ((relationship == null) ? 0 : relationship.hashCode()); 226 | return result; 227 | } 228 | 229 | @Override 230 | public boolean equals(Object obj) { 231 | if (this == obj) 232 | return true; 233 | if (obj == null) 234 | return false; 235 | if (getClass() != obj.getClass()) 236 | return false; 237 | FaultingSet other = (FaultingSet) obj; 238 | if (backingSet == null) { 239 | if (other.backingSet != null) 240 | return false; 241 | } else if (!backingSet.equals(other.backingSet)) 242 | return false; 243 | if (parent == null) { 244 | if (other.parent != null) 245 | return false; 246 | } else if (!parent.equals(other.parent)) 247 | return false; 248 | if (relationship == null) { 249 | if (other.relationship != null) 250 | return false; 251 | } else if (!relationship.equals(other.relationship)) 252 | return false; 253 | return true; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/IncrementalStore.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import com.github.dkharrat.nexusdata.metamodel.Entity; 9 | import com.github.dkharrat.nexusdata.metamodel.Relationship; 10 | 11 | /** 12 | * An IncrementalStore represents a persistence store in which data is loaded or saved incrementally as needed. It is 13 | * typically used when the entire data set cannot fit in-memory, and must be "paged-in" into memory on-demand. This is 14 | * an abstract class that can be implemented to provide support for such persistence stores. 15 | */ 16 | public abstract class IncrementalStore extends PersistentStore { 17 | 18 | public IncrementalStore(URL location) { 19 | super(location); 20 | } 21 | 22 | public IncrementalStore(File location) { 23 | super(location); 24 | } 25 | 26 | @Override 27 | protected abstract 28 | List executeFetchRequest(FetchRequest request, ObjectContext context); 29 | 30 | @Override 31 | protected abstract void executeSaveRequest(SaveChangesRequest request, ObjectContext context); 32 | 33 | @Override 34 | protected abstract StoreCacheNode getObjectValues(ObjectID objectID, ObjectContext context); 35 | 36 | @Override 37 | protected abstract ObjectID getToOneRelationshipValue(ObjectID objectID, Relationship relationship, ObjectContext context); 38 | 39 | @Override 40 | protected abstract Set getToManyRelationshipValue(ObjectID objectID, Relationship relationship, ObjectContext context); 41 | 42 | @Override 43 | protected abstract List getPermanentIDsForObjects(List objects); 44 | 45 | @Override 46 | protected ObjectID createObjectID(Entity entity, Object referenceObject) { 47 | return super.createObjectID(entity, referenceObject); 48 | } 49 | 50 | @Override 51 | public Object getReferenceObjectForObjectID(ObjectID objectID) { 52 | return super.getReferenceObjectForObjectID(objectID); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/NoSuchPropertyException.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import com.github.dkharrat.nexusdata.metamodel.Entity; 4 | 5 | /** 6 | * This exception is thrown when a non-existent property is accessed. 7 | */ 8 | public class NoSuchPropertyException extends RuntimeException { 9 | private String propertyName; 10 | 11 | public NoSuchPropertyException(ManagedObject object, String propertyName) { 12 | super("No such property '" + propertyName + "' in object " + object); 13 | this.propertyName = propertyName; 14 | } 15 | 16 | public NoSuchPropertyException(Entity entity, String propertyName) { 17 | super("No such property '" + propertyName + "' in entity " + entity); 18 | this.propertyName = propertyName; 19 | } 20 | 21 | /** 22 | * Returns the name of the property that was attempted to be accessed. 23 | * @return the name of the property that was attempted to be accessed 24 | */ 25 | public String getPropertyName() { 26 | return propertyName; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/ObjectContextNotifier.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedHashSet; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | /** 9 | * This is a singleton class used to register for notifications from {@link ObjectContext} events, such as 10 | * inserted objects, changed objects, etc. 11 | */ 12 | public class ObjectContextNotifier { 13 | 14 | /** 15 | * Defines the interface which {@link ObjectContext} listeners must implement to receive notifications of relevant 16 | * events. 17 | */ 18 | public static interface ObjectContextListener { 19 | /** 20 | * Called when any set of objects have changed since the last event loop. Changes includes objects that have 21 | * have been inserted, removed, updated, or refreshed. 22 | * 23 | * @param context the context in which the changed objects belong to 24 | * @param changedObjects a notification object containing the objects that have been inserted, removed, 25 | * updated, or refreshed 26 | */ 27 | public void onObjectsChanged(ObjectContext context, ObjectsChangedNotification changedObjects); 28 | 29 | /** 30 | * Called when the context is about to be saved. 31 | * 32 | * @param context the context instance that will be saved 33 | */ 34 | public void onPreSave(ObjectContext context); 35 | 36 | /** 37 | * Called when the context has just been saved. 38 | * 39 | * @param context the context instance that has been saved 40 | * @param changedObjects a reference to a collection of the objects that have been changed 41 | */ 42 | public void onPostSave(ObjectContext context, ChangedObjectsSet changedObjects); 43 | } 44 | 45 | /** 46 | * Provides a default empty implementation of the {@link ObjectContextListener}. This is useful when you want 47 | * to only override specific methods and leave the rest empty. 48 | */ 49 | public static abstract class DefaultObjectContextListener implements ObjectContextListener { 50 | @Override 51 | public void onPreSave(ObjectContext context) { 52 | // do nothing by default 53 | } 54 | 55 | @Override 56 | public void onObjectsChanged(ObjectContext context, ObjectsChangedNotification objectsChanged) { 57 | // do nothing by default 58 | } 59 | 60 | @Override 61 | public void onPostSave(ObjectContext context, ChangedObjectsSet changedObjects) { 62 | // do nothing by default 63 | } 64 | } 65 | 66 | private static Set allContextsListeners = new LinkedHashSet(); 67 | private static Map> contextListeners = new HashMap>(); 68 | 69 | static void notifyListenersOfPreSave(ObjectContext context) { 70 | for (ObjectContextListener listener : getListeners(context)) { 71 | listener.onPreSave(context); 72 | } 73 | } 74 | 75 | static void notifyListenersOfPostSave(ObjectContext context, ChangedObjectsSet changedObjects) { 76 | for (ObjectContextListener listener : getListeners(context)) { 77 | listener.onPostSave(context, changedObjects); 78 | } 79 | } 80 | 81 | static void notifyListenersOfObjectsChanged(ObjectContext context, ObjectsChangedNotification changedObjects) { 82 | for (ObjectContextListener listener : getListeners(context)) { 83 | listener.onObjectsChanged(context, changedObjects); 84 | } 85 | } 86 | 87 | static boolean hasListeners(ObjectContext context) { 88 | Set listeners = contextListeners.get(context); 89 | return !allContextsListeners.isEmpty() || (listeners != null && !listeners.isEmpty()); 90 | } 91 | 92 | private static Set getListeners(ObjectContext context) { 93 | Set allListeners = getListenersForContext(context); 94 | 95 | if (!allContextsListeners.isEmpty()) { 96 | // make a copy so we don't modify original 97 | allListeners = new LinkedHashSet(allListeners); 98 | allListeners.addAll(allContextsListeners); 99 | } 100 | 101 | return allListeners; 102 | } 103 | 104 | private static Set getListenersForContext(ObjectContext context) { 105 | Set listeners = contextListeners.get(context); 106 | 107 | if (listeners == null) { 108 | listeners = new LinkedHashSet(); 109 | contextListeners.put(context, listeners); 110 | } 111 | 112 | return listeners; 113 | } 114 | 115 | /** 116 | * Registers a listener to be notified of events from the specified ObjectContext. If the listener was previously 117 | * registered for events from all ObjectContexts, registration is ignored. 118 | *

119 | * Note: An application must unregister the listener when it is no longer interested in the events to prevent 120 | * memory leaks. 121 | * 122 | * @param forContext the ObjectContext to listen for 123 | * @param listener the listener to register 124 | * @see #registerListener(ObjectContextListener) 125 | */ 126 | public static void registerListener(ObjectContext forContext, ObjectContextListener listener) { 127 | if (!allContextsListeners.contains(listener)) { 128 | Set listeners = getListenersForContext(forContext); 129 | listeners.add(listener); 130 | } 131 | } 132 | 133 | /** 134 | * Registers a listener to be notified of events from all ObjectContexts used by the application. 135 | *

136 | * Note: An application must unregister the listener when you are no longer interested in the events to prevent 137 | * memory leaks. 138 | * 139 | * @param listener the listener to register 140 | * @see #registerListener(ObjectContext, ObjectContextListener) 141 | */ 142 | public static void registerListener(ObjectContextListener listener) { 143 | allContextsListeners.add(listener); 144 | } 145 | 146 | /** 147 | * Unregisters a listener from receiving events for the specified object context. 148 | * 149 | * @param forContext the ObjectContext to unregister for 150 | * @param listener the listener to unregister 151 | * @see #unregisterListener(ObjectContextListener) 152 | */ 153 | public static void unregisterListener(ObjectContext forContext, ObjectContextListener listener) { 154 | Set listeners = getListenersForContext(forContext); 155 | listeners.remove(listener); 156 | if (listeners.isEmpty()) { 157 | // remove context from collection to prevent memory leaks 158 | contextListeners.remove(forContext); 159 | } 160 | } 161 | 162 | /** 163 | * Unregisters a listener from receiving events for all ObjectContexts used by the application. 164 | * 165 | * @param listener the listener to unregister 166 | * @see #unregisterListener(ObjectContext, ObjectContextListener) 167 | */ 168 | public static void unregisterListener(ObjectContextListener listener) { 169 | allContextsListeners.remove(listener); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/ObjectID.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.net.URI; 4 | 5 | import com.github.dkharrat.nexusdata.metamodel.Entity; 6 | 7 | /** 8 | * An ObjectID represents a global identifier that uniquely identifies a {@link ManagedObject}. The identifier is 9 | * persisted across multiple runs of the application for the same ManagedObject. Moreover, two different instances of a 10 | * ManagedObject referring to the same backing record in the persistence store will have the same unique ObjectID. 11 | */ 12 | public class ObjectID { 13 | private final PersistentStore store; 14 | private final Entity entity; 15 | private final Object id; 16 | 17 | ObjectID(PersistentStore store, Entity entity, Object referenceObject) { 18 | this.store = store; 19 | this.entity = entity; 20 | id = referenceObject; 21 | } 22 | 23 | /** 24 | * Returns the persistence store associated with this ObjectID. 25 | * 26 | * @return the persistence store associated with this ObjectID 27 | */ 28 | public PersistentStore getPersistentStore() { 29 | return store; 30 | } 31 | 32 | Class getType() { 33 | return entity.getType(); 34 | } 35 | 36 | /** 37 | * Returns the corresponding entity of this ObjectID. 38 | * 39 | * @return the corresponding entity of this ObjectID 40 | */ 41 | public Entity getEntity() { 42 | return entity; 43 | } 44 | 45 | Object getReferenceObject() { 46 | return id; 47 | } 48 | 49 | /** 50 | * Indicates whether this ObjectID is a temporary one and can change at some point in the future. When inserting a 51 | * new {@link ManagedObject} into an ObjectContext, it is assigned a temporary ID until it is saved, after which it 52 | * is allocated a unique permanent ID by the persistence store. When the ObjectID is temporary, do not keep a 53 | * permanent reference to it, since it is not guaranteed to stay the same. If you need a permanent ID assigned for 54 | * an object, you can request one from {@link ObjectContext#obtainPermanentIDsForObjects(java.util.Collection)}. 55 | * 56 | * @return true if this ObjectID is a temporary one, or false otherwise 57 | */ 58 | public boolean isTemporary() { 59 | return store == null; 60 | } 61 | 62 | /** 63 | * Returns a URI-representation of this ObjectID, which can be used for persistence purposes. If the ObjectID is 64 | * temporary, this representation can change after the corresponding ManagedObject is saved. Persisted objects or 65 | * objects that have a permanent ID are guaranteed to return the same representation. 66 | * 67 | * nexusdata://<> 68 | * 69 | * @return a URI-representation of this ObjectID 70 | */ 71 | public URI getUriRepresentation() { 72 | StringBuilder sb = new StringBuilder("nexusdata://"); 73 | if (store != null) { 74 | sb.append(store.getUuid()); 75 | } 76 | sb.append("/").append(entity.getName()).append("/").append(id.toString()); 77 | return URI.create(sb.toString()); 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | final int prime = 31; 83 | int result = 1; 84 | result = prime * result + ((entity == null) ? 0 : entity.hashCode()); 85 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 86 | result = prime * result + ((store == null) ? 0 : store.hashCode()); 87 | return result; 88 | } 89 | 90 | /** 91 | * Determines whether this ObjectID is equivalent to another ObjectID. 92 | * 93 | * @param obj the other object to compare against. If a non-ObjectID is passed, this method returns false. 94 | * 95 | * @return true if this ObjectID is equivalent to the other specified ObjectID, or false otherwise 96 | */ 97 | @Override 98 | public boolean equals(Object obj) { 99 | if (this == obj) 100 | return true; 101 | if (obj == null) 102 | return false; 103 | if (getClass() != obj.getClass()) 104 | return false; 105 | ObjectID other = (ObjectID) obj; 106 | if (entity == null) { 107 | if (other.entity != null) 108 | return false; 109 | } else if (!entity.equals(other.entity)) 110 | return false; 111 | if (id == null) { 112 | if (other.id != null) 113 | return false; 114 | } else if (!id.equals(other.id)) 115 | return false; 116 | if (store == null) { 117 | if (other.store != null) 118 | return false; 119 | } else if (!store.equals(other.store)) 120 | return false; 121 | return true; 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return "<" + getUriRepresentation() + ">"; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/ObjectsChangedNotification.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * Represents the event that is sent when a set of objects in an {@link ObjectContext} have changed. This event is sent 8 | * out from {@link ObjectContextNotifier}. 9 | * 10 | * @see ObjectContextNotifier.ObjectContextListener#notifyListenersOfObjectsChanged(ObjectContext, ObjectsChangedNotification) 11 | */ 12 | public class ObjectsChangedNotification extends ChangedObjectsSet { 13 | 14 | private final Set refreshedObjects; 15 | 16 | ObjectsChangedNotification() { 17 | this(new HashSet(), new HashSet(), new HashSet(), new HashSet()); 18 | } 19 | 20 | ObjectsChangedNotification(Set insertedObjects, 21 | Set updatedObjects, 22 | Set deletedObjects, 23 | Set refreshedObjects) { 24 | super(insertedObjects, updatedObjects, deletedObjects); 25 | this.refreshedObjects = refreshedObjects; 26 | } 27 | 28 | ObjectsChangedNotification(ChangedObjectsSet changedObjects, 29 | Set refreshedObjects) { 30 | this(changedObjects.getInsertedObjects(), changedObjects.getUpdatedObjects(), changedObjects.getDeletedObjects(), refreshedObjects); 31 | } 32 | 33 | @Override 34 | void clear() { 35 | super.clear(); 36 | refreshedObjects.clear(); 37 | } 38 | 39 | /** 40 | * Returns the objects that have been refreshed in the context (see {@link ManagedObject#refresh()} for details on 41 | * refreshed objects. 42 | * 43 | * @return the set of objects that have been refreshed 44 | */ 45 | public Set getRefreshedObjects() { 46 | return refreshedObjects; 47 | } 48 | 49 | void objectRefreshed(ManagedObject object) { 50 | refreshedObjects.add(object); 51 | } 52 | 53 | /** 54 | * Indicates whether the specified object has been refreshed. 55 | * 56 | * @param object the object to check whether it was refreshed 57 | * 58 | * @return true if the specified object has been refreshed, or false otherwise 59 | */ 60 | public boolean isRefreshed(ManagedObject object) { 61 | return refreshedObjects.contains(object); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/PersistentStoreCoordinator.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.net.URI; 4 | import java.util.ArrayList; 5 | import java.util.LinkedHashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | 10 | import com.github.dkharrat.nexusdata.metamodel.Entity; 11 | import com.github.dkharrat.nexusdata.metamodel.ObjectModel; 12 | import com.github.dkharrat.nexusdata.utils.StringUtil; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * A PersistentStoreCoordinator is a mediator between a {@link ObjectContext} and other {@link PersistentStore}s. 18 | * It is used by an {@link ObjectContext} to obtain model information and save the object graph. From the perspective of 19 | * the ObjectContext, a PersistentStoreCoordinator behaves such that a group of PersistentStores appear as one virtual 20 | * store. This allows the ObjectContext to create the corresponding object graph from the union of the persistence 21 | * stores that this coordinator covers. 22 | */ 23 | public class PersistentStoreCoordinator { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(PersistentStoreCoordinator.class); 26 | 27 | private final Map storeUuidToPersistentStore = new LinkedHashMap(); 28 | private final ObjectModel model; 29 | 30 | /** 31 | * Creates a PersistentStoreCoordinator associated with the specific model. 32 | * 33 | * @param model the model that this PersistentStoreCoordinator will use 34 | */ 35 | public PersistentStoreCoordinator(ObjectModel model) { 36 | this.model = model; 37 | } 38 | 39 | /** 40 | * Adds a persistent store to this coordinator. A persistent store cannot belong to multiple coordinators. 41 | * 42 | * @param store the persistent store to add 43 | */ 44 | public void addStore(PersistentStore store) { 45 | if (store.getCoordinator() != null && store.getCoordinator() != this) { 46 | throw new IllegalStateException("PersistentStore " + store + " already assigned to another coordinator"); 47 | } 48 | store.setPersistentStoreCoordinator(this); 49 | store.loadMetadata(); 50 | 51 | if (store.getUuid() == null) { 52 | throw new RuntimeException("Did not get permanent UUID from store: " + store); 53 | } 54 | 55 | storeUuidToPersistentStore.put(store.getUuid(), store); 56 | 57 | LOG.info("Added persistent store " + store); 58 | } 59 | 60 | /** 61 | * Removes the specified persistent store from this coordinator. 62 | * 63 | * @param store the store to remove 64 | */ 65 | public void removeStore(PersistentStore store) { 66 | storeUuidToPersistentStore.remove(store.getUuid()); 67 | store.setPersistentStoreCoordinator(null); 68 | } 69 | 70 | /** 71 | * Returns the persistent store contained in this coordinator that is identified by the specified ID. 72 | * 73 | * @param uuid the UUID of the persistent store to return 74 | * 75 | * @return the persistent store in this coordinator that matches the specified ID. If no such store exists, 76 | * {@code null} is returned. 77 | */ 78 | public PersistentStore getPersistentStore(UUID uuid) { 79 | return storeUuidToPersistentStore.get(uuid); 80 | } 81 | 82 | /** 83 | * Returns all the persistent stores contained within this coordinator. 84 | * 85 | * @return all the persistent stores contained within this coordinator 86 | */ 87 | public List getPersistentStores() { 88 | return new ArrayList(storeUuidToPersistentStore.values()); 89 | } 90 | 91 | /** 92 | * Returns the model associated with this coordinator. 93 | * 94 | * @return the model associated with this coordinator 95 | */ 96 | public ObjectModel getModel() { 97 | return model; 98 | } 99 | 100 | /** 101 | * Returns the ObjectID from the corresponding URI representation. 102 | * 103 | * @param objectIDUri the URI that represents the ObjectID 104 | * 105 | * @return the ObjectID represented by the specified URI 106 | * @throws {@link IllegalArgumentException} if the URI represents a temporary ID or the URI is invalid 107 | */ 108 | public ObjectID objectIDFromUri(URI objectIDUri) { 109 | if (!objectIDUri.getScheme().equals("nexusdata")) { 110 | throw new IllegalArgumentException(""); 111 | } else if (StringUtil.isBlank(objectIDUri.getAuthority())) { 112 | throw new IllegalArgumentException("Cannot create ObjectID from temporary ID"); 113 | } 114 | 115 | String[] parts = objectIDUri.getPath().split("/"); 116 | if (parts.length < 3 || parts[1].isEmpty() || parts[2].isEmpty()) { 117 | throw new IllegalArgumentException("Invalid ObjectID URI format"); 118 | } 119 | 120 | UUID storeUuid = UUID.fromString(objectIDUri.getAuthority()); 121 | Entity entity = model.getEntity(parts[1]); 122 | Object referenceObject = parts[2]; 123 | try { 124 | referenceObject = Long.parseLong(parts[2]); 125 | } catch (NumberFormatException e) { 126 | // ignore; treat ref object as string 127 | } 128 | 129 | return new ObjectID(storeUuidToPersistentStore.get(storeUuid), entity, referenceObject); 130 | } 131 | 132 | //TODO: implement - must be synchronized? 133 | void executeFetchRequest() { 134 | 135 | } 136 | 137 | //TODO: implement - must be synchronized? 138 | void save(SaveChangesRequest saveRequest) { 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/PersistentStoreRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | /** 4 | * Represents a request (e.g. saving, querying, etc.) sent to a persistence store 5 | */ 6 | interface PersistentStoreRequest { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/SaveChangesRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | /** 4 | * Represents a save request to a persistence store, containing all the objects that have been changed, either by 5 | * insertion, deletion, or updating, and are to be reflected in the persistence store. This request is sent by the 6 | * {@link com.github.dkharrat.nexusdata.core.ObjectContext} when a save is performed. 7 | */ 8 | public class SaveChangesRequest implements PersistentStoreRequest { 9 | 10 | private final ChangedObjectsSet changedObjects; 11 | 12 | SaveChangesRequest(ChangedObjectsSet changedObjects) { 13 | this.changedObjects = changedObjects; 14 | } 15 | 16 | /** 17 | * Returns all the objects that have been changed. 18 | * 19 | * @return Returns all the objects that need to be inserted 20 | */ 21 | public ChangedObjectsSet getChanges() { 22 | return changedObjects; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/SortDescriptor.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | /** 4 | * This class describes how elements within a collection are sorted. Sorting is defined by the attribute name to sort on 5 | * and the direction of the sort (ascending/descending) 6 | */ 7 | public class SortDescriptor { 8 | 9 | private final String attributeName; 10 | private final boolean isAscending; 11 | 12 | /** 13 | * Creates a new SortDescriptor 14 | * 15 | * @param attributeName the attribute name to sort on 16 | * @param isAscending the direction to sort on. If true, elements will be in ascending order, or otherwise 17 | * in descending order. 18 | */ 19 | public SortDescriptor(String attributeName, boolean isAscending) { 20 | this.attributeName = attributeName; 21 | this.isAscending = isAscending; 22 | } 23 | 24 | /** 25 | * Returns the attribute name that is sorted on 26 | * 27 | * @return Returns the attribute name that is sorted on 28 | */ 29 | public String getAttributeName() { 30 | return attributeName; 31 | } 32 | 33 | /** 34 | * Returns the direction of the sort 35 | * 36 | * @return if true, sorting is done in ascending order. Otherwise, it's in descending order. 37 | */ 38 | public boolean isAscending() { 39 | return isAscending; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "SortDescriptor{" + 45 | "attributeName='" + attributeName + '\'' + 46 | ", isAscending=" + isAscending + 47 | '}'; 48 | } 49 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/core/StoreCacheNode.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.core; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Represents the data cache for a single record in the persistent store. It's used by the framework to retrieve and 8 | * store the data of the corresponding managed object from and to the persistent store. 9 | * 10 | * Internally, a StoreCacheNode is simply a key/value store to get or set value of individual properties. 11 | * A value can contain references to other ObjectIDs if the property is a relationship. 12 | */ 13 | public class StoreCacheNode { 14 | private final ObjectID id; 15 | private Map properties = new HashMap(); 16 | 17 | /** 18 | * Creates a new StoreCacheNode that's associated with the specified ObjectID 19 | * 20 | * @param id the objectID that this node will represent 21 | */ 22 | public StoreCacheNode(ObjectID id) { 23 | this.id = id; 24 | } 25 | 26 | /** 27 | * Returns the ObjectID represented by this StoreCacheNode. 28 | * 29 | * @return the ObjectID represented by this StoreCacheNode 30 | */ 31 | public ObjectID getID() { 32 | return id; 33 | } 34 | 35 | /** 36 | * Sets the value for the given property. For attributes, the value must be of type that is supported, like {@link String}, 37 | * {@link Integer}, {@link java.util.Date}, {@link Enum}, etc. For a to-one relationship, the value must be the 38 | * {@link ObjectID} of the related object. For a to-many relationship, the value must be the a {@code Set} 39 | * containing the ObjectIDs of the related objects. 40 | * 41 | * @param name the property name to set 42 | * @param value the value of the property 43 | */ 44 | public void setProperty(String name, Object value) { 45 | properties.put(name, value); 46 | } 47 | 48 | /** 49 | * Sets this node to have the properties specified from the input map. Previously set properties in this node will 50 | * be removed. 51 | * 52 | * @param props a Map containing the key/value pairs for the properties to be set. For keys representing attributes, 53 | * their value must be of type that is supported, like {@link String}, {@link Integer}, 54 | * {@link java.util.Date}, {@link Enum}, etc. For keys representing a to-one relationship, the value 55 | * must be the {@link ObjectID} of the related object. For a to-many relationship, the value must be 56 | * the a {@code Set} containing the ObjectIDs of the related objects. 57 | */ 58 | public void setProperties(Map props) { 59 | properties.clear(); 60 | properties.putAll(props); 61 | } 62 | 63 | /** 64 | * Returns the value of the given property. For attributes, the value will be of type that is one of the supported types like {@link String}, 65 | * {@link Integer}, {@link java.util.Date}, {@link Enum}, etc. For a to-one relationship, the value must be the 66 | * {@link ObjectID} of the related object. For a to-many relationship, the value must be the a {@code Set} 67 | * containing the ObjectIDs of the related objects. 68 | * 69 | * @param name the property name to get 70 | * 71 | * @return the value of the specified property 72 | */ 73 | public Object getProperty(String name) { 74 | return properties.get(name); 75 | } 76 | 77 | /** 78 | * Returns true if this node is storing a value for the specified property, or false otherwise. 79 | * 80 | * @param name the property name to check 81 | * 82 | * @return true if this node is storing a value for the specified property, or false otherwise 83 | */ 84 | public boolean hasProperty(String name) { 85 | return properties.containsKey(name); 86 | } 87 | 88 | /** 89 | * Returns the key/value store for all the properties in this node. 90 | * 91 | * @return the key/value store for all the properties in this node 92 | */ 93 | public Map getProperties() { 94 | return properties; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/metamodel/Attribute.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.metamodel; 2 | 3 | import com.github.dkharrat.nexusdata.core.ManagedObject; 4 | 5 | /** 6 | * An Attribute is a {@link ManagedObject} {@link Property} that represents a field that stores a concrete value (e.g. 7 | * an Integer, String, Date, etc.). 8 | */ 9 | public class Attribute extends Property { 10 | 11 | private Object defaultValue; 12 | 13 | /** 14 | * Creates a new Attribute. 15 | * 16 | * @param entity the associated entity 17 | * @param name the name of the property 18 | * @param type the property type 19 | * @param isRequired if true, property is required to have a value 20 | * @param defaultValue the default value to initialize the attribute when constructing a new {@link ManagedObject} 21 | */ 22 | public Attribute(Entity entity, String name, Class type, boolean isRequired, Object defaultValue) { 23 | super(entity, name, type, isRequired); 24 | if (defaultValue != null && !getType().isAssignableFrom(defaultValue.getClass())) { 25 | throw new IllegalArgumentException("Type of defaultValue '" + defaultValue + "' is not compatible with type of this attribute (" + getType() + ")"); 26 | } 27 | this.defaultValue = defaultValue; 28 | } 29 | 30 | /** 31 | * Returns the default value for this attribute. 32 | * 33 | * @return the default value for this attribute 34 | */ 35 | public Object getDefaultValue() { 36 | return defaultValue; 37 | } 38 | 39 | @Override 40 | public boolean isRelationship() { 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/metamodel/Entity.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.metamodel; 2 | 3 | import java.util.*; 4 | 5 | import com.github.dkharrat.nexusdata.core.ManagedObject; 6 | import com.github.dkharrat.nexusdata.core.NoSuchPropertyException; 7 | 8 | /** 9 | * An Entity represents the {@link ManagedObject}'s metadata. It is analogous to a Java's class. An instance of an 10 | * Entity is of supertype {@code ManagedObject}. An entity is composed of multiple properties, which are analogous to a 11 | * class' member fields. 12 | */ 13 | public class Entity { 14 | 15 | private final ObjectModel model; 16 | private final Class type; 17 | private Entity superEntity; 18 | private final Set> subEntities = new HashSet<>(); 19 | private final Map properties = new HashMap(); 20 | 21 | /** 22 | * Creates a new Entity. 23 | * 24 | * @param model the model which this entity belongs to 25 | * @param type the instance type of this entity 26 | */ 27 | public Entity(ObjectModel model, Class type) { 28 | this.model = model; 29 | this.type = type; 30 | } 31 | 32 | /** 33 | * Returns the model which this entity belongs to. 34 | * 35 | * @return the model which this entity belongs to 36 | */ 37 | public ObjectModel getModel() { 38 | return model; 39 | } 40 | 41 | /** 42 | * Returns the instance type of this entity, which is what will be instantiated when a new instance of this entity 43 | * created. 44 | * 45 | * @return the instance type of this entity 46 | */ 47 | public Class getType() { 48 | return type; 49 | } 50 | 51 | /** 52 | * Returns the Entity from which this Entity inherits, or NULL if there is no super-entity. 53 | */ 54 | public Entity getSuperEntity() { 55 | return superEntity; 56 | } 57 | 58 | /** 59 | * Returns the set of entities that inherit from this Entity. 60 | */ 61 | public Set> getSubEntities() { 62 | return Collections.unmodifiableSet(subEntities); 63 | } 64 | 65 | void setSuperEntity(Entity superEntity) { 66 | if (this.superEntity != null) throw new AssertionError("Super entity already set!"); 67 | 68 | this.superEntity = superEntity; 69 | superEntity.subEntities.add(this); 70 | } 71 | 72 | /** 73 | * Returns true if this is the root entity. In other words, it does not extend any other entity. 74 | */ 75 | public boolean isBaseEntity() { 76 | return this.superEntity == null; 77 | } 78 | 79 | /** 80 | * Returns the root entity in the inheritance tree. Calling this method on the root entity will return itself. 81 | */ 82 | public Entity getTopMostSuperEntity() { 83 | if (this.superEntity == null) { 84 | return this; 85 | } else { 86 | return this.superEntity.getTopMostSuperEntity(); 87 | } 88 | } 89 | 90 | void addProperty(Property property) { 91 | if (properties.containsKey(property.getName())) { 92 | throw new IllegalArgumentException(property + " already exists in entity " + getName()); 93 | } 94 | properties.put(property.getName(), property); 95 | } 96 | 97 | void removeProperty(String name) { 98 | properties.remove(name); 99 | } 100 | 101 | /** 102 | * Indicates whether this entity defined the specified property name. 103 | * 104 | * @param name the name of the property to check 105 | * 106 | * @return true if the specified property name is part of this entity, or false otherwise 107 | */ 108 | public boolean hasProperty(String name) { 109 | return properties.containsKey(name); 110 | } 111 | 112 | /** 113 | * Returns the set of properties (attributes and relationships) defined on this entity. 114 | * 115 | * @return the set of properties defined on this entity 116 | */ 117 | public Collection getProperties() { 118 | return properties.values(); 119 | } 120 | 121 | /** 122 | * Returns the set of attributes defined on this entity. 123 | * 124 | * @return the set of attributes defined on this entity 125 | */ 126 | public Collection getAttributes() { 127 | List attributes = new ArrayList(); 128 | for (Property property : getProperties()) { 129 | if (property instanceof Attribute) { 130 | attributes.add((Attribute)property); 131 | } 132 | } 133 | 134 | return attributes; 135 | } 136 | 137 | /** 138 | * Returns the set of relationships defined on this entity. 139 | * 140 | * @return the set of relationships defined on this entity 141 | */ 142 | public Collection getRelationships() { 143 | List relationships = new ArrayList(); 144 | for (Property property : getProperties()) { 145 | if (property instanceof Relationship) { 146 | relationships.add((Relationship)property); 147 | } 148 | } 149 | 150 | return relationships; 151 | } 152 | 153 | /** 154 | * Returns the property instance from the specified name. The property must exist, or an exception is thrown. 155 | * 156 | * @param name the name of the property to return 157 | * 158 | * @return the property for the specified name 159 | * @throws {@link NoSuchPropertyException} if no such property exists 160 | */ 161 | public Property getProperty(String name) { 162 | Property property = properties.get(name); 163 | 164 | if (property == null) { 165 | throw new NoSuchPropertyException(this, name); 166 | } 167 | 168 | return property; 169 | } 170 | 171 | /** 172 | * Returns the attribute instance from the specified name. The attribute must exist, or an exception is thrown. 173 | * 174 | * @param name the name of the attribute to return 175 | * 176 | * @return the attribute for the specified name 177 | * @throws {@link NoSuchPropertyException} if no such attribute exists 178 | * {@link IllegalArgumentException} if the specified name is not an attribute 179 | */ 180 | public Attribute getAttribute(String name) { 181 | Property property = getProperty(name); 182 | if (property instanceof Attribute) { 183 | return (Attribute) property; 184 | } 185 | 186 | throw new IllegalArgumentException("Property '"+name+"' is not an attribute."); 187 | } 188 | 189 | /** 190 | * Returns the relationship instance from the specified name. The relationship must exist, or an exception is thrown. 191 | * 192 | * @param name the name of the relationship to return 193 | * 194 | * @return the relationship for the specified name 195 | * @throws {@link NoSuchPropertyException} if no such relationship exists 196 | * {@link IllegalArgumentException} if the specified name is not a relationship 197 | */ 198 | public Relationship getRelationship(String name) { 199 | Property property = getProperty(name); 200 | if (property instanceof Relationship) { 201 | return (Relationship) property; 202 | } 203 | 204 | throw new IllegalArgumentException("Property '"+name+"' is not a relationship."); 205 | } 206 | 207 | /** 208 | * Returns the name of this entity. 209 | * 210 | * @return the name of this entity 211 | */ 212 | public String getName() { 213 | return type.getSimpleName(); 214 | } 215 | 216 | @Override 217 | public int hashCode() { 218 | final int prime = 31; 219 | int result = 1; 220 | result = prime * result + ((type == null) ? 0 : type.hashCode()); 221 | return result; 222 | } 223 | 224 | @Override 225 | public boolean equals(Object obj) { 226 | if (this == obj) 227 | return true; 228 | if (obj == null) 229 | return false; 230 | if (getClass() != obj.getClass()) 231 | return false; 232 | Entity other = (Entity) obj; 233 | if (type == null) { 234 | if (other.type != null) 235 | return false; 236 | } else if (!type.equals(other.type)) 237 | return false; 238 | return true; 239 | } 240 | 241 | @Override 242 | public String toString() { 243 | return "Entity [" 244 | + "name=" + getName() 245 | + ", class=" + getType().getName() 246 | + ", properties=" + properties.values() 247 | + "]"; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/metamodel/ObjectModel.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.metamodel; 2 | 3 | import java.io.*; 4 | import java.util.*; 5 | 6 | import com.github.dkharrat.nexusdata.core.ManagedObject; 7 | 8 | /** 9 | * An ObjectModel represents the collection of {@link Entity}s that defines the types of objects that exist in a 10 | * persistence store. 11 | */ 12 | public class ObjectModel { 13 | private final int version; 14 | private final String name; 15 | private final Map> entities = new HashMap>(); 16 | 17 | /** 18 | * Creates a new object model. 19 | * 20 | * @param name the name of the model 21 | * @param entities the set of entities defined in this model 22 | * @param version the version of this model 23 | */ 24 | public ObjectModel(String name, Collection> entities, int version) { 25 | this.name = name; 26 | this.version = version; 27 | initEntities(entities); 28 | } 29 | 30 | /** 31 | * Creates a new ObjectModel from a model file 32 | * 33 | * @param modelData the input stream for the model file 34 | * @param includePath path to look under if there are other models to include 35 | * @throws IOException if there was a problem reading from the input stream 36 | */ 37 | public ObjectModel(InputStream modelData, String includePath) throws IOException { 38 | ObjectModelJsonParser.ParsedModel parsedModel = ObjectModelJsonParser.parseJsonModel(this, modelData, includePath); 39 | name = parsedModel.getName(); 40 | version = parsedModel.getVersion(); 41 | initEntities(parsedModel.getEntities()); 42 | } 43 | 44 | /** 45 | * Creates a new ObjectModel from a model file 46 | * 47 | * @param modelData the input stream for the model file 48 | * @throws IOException if there was a problem reading from the input stream 49 | */ 50 | public ObjectModel(InputStream modelData) throws IOException { 51 | this(modelData, ""); 52 | } 53 | 54 | /** 55 | * Merges two unrelated models (as in not referencing each other) together to form a new single model. This is 56 | * useful for organizational purposes, where two unrelated models are stored in separate files. The models must not 57 | * conflict together (e.g. sharing the same entity name). 58 | * 59 | * @param models the set of models to merge 60 | * @param name the name of the newly merged model 61 | * @param version the version of the newly merged model 62 | * 63 | * @return a new ObjectModel from merging the specified models together 64 | */ 65 | public static ObjectModel mergeModels(Set models, String name, int version) { 66 | Set> mergedEntities = new HashSet<>(); 67 | for (ObjectModel model : models) { 68 | mergedEntities.addAll(model.getEntities()); 69 | } 70 | 71 | return new ObjectModel(name, mergedEntities, version); 72 | } 73 | 74 | private void initEntities(Collection> entities) { 75 | for (Entity entity : entities) { 76 | if (entities.contains(entity.getName())) { 77 | throw new RuntimeException("Entity " + entity.getName() + " already exists in this model."); 78 | } 79 | this.entities.put(entity.getName(), entity); 80 | } 81 | } 82 | 83 | /** 84 | * Returns the model version. Model versions are used to identify different versions of the same semantic model. 85 | * When making changes to a model that has already been published and is in use, make sure to increment the version 86 | * number to have persistence stores using older versions of the model to upgrade. 87 | * 88 | * @return an integer representing the model's version 89 | */ 90 | public int getVersion() { 91 | return version; 92 | } 93 | 94 | /** 95 | * Returns the model's name. 96 | * 97 | * @return the model's name. 98 | */ 99 | public String getName() { 100 | return name; 101 | } 102 | 103 | /** 104 | * Returns the set of entities defined in this model. 105 | * 106 | * @return the set of entities defined in this model 107 | */ 108 | public Set> getEntities() { 109 | return new HashSet>(entities.values()); 110 | } 111 | 112 | /** 113 | * Returns the entity in this model by the entity's class type, or NULL if not found. 114 | * 115 | * @param type the class type 116 | */ 117 | @SuppressWarnings("unchecked") 118 | public Entity getEntity(Class type) { 119 | return (Entity) getEntity(type.getSimpleName()); 120 | } 121 | 122 | /** 123 | * Returns the entity in this model by the entity's name, or NULL if not found. 124 | * 125 | * @param name the name of the entity 126 | */ 127 | @SuppressWarnings("unchecked") 128 | public Entity getEntity(String name) { 129 | return (Entity)entities.get(name); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/metamodel/Property.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.metamodel; 2 | 3 | 4 | // TODO: make it use generic type ? 5 | 6 | /** 7 | * A Property is an abstract representation of an object's field metadata. There are two kinds of properties: 8 | * {@link Attribute}s and {@link Relationship}s. These classes provide support for reflection on an ManagedObject. 9 | * 10 | * Each property is associated with a specific {@link Entity} and has a name. A property can also have certain 11 | * constraints, such as whether it's required or not. 12 | */ 13 | public abstract class Property { 14 | 15 | private final Entity entity; 16 | private final String name; 17 | private final Class type; //TODO: should this be moved to Attribute, as it doesn't really make sense for relationships 18 | private final boolean isRequired; //TODO: what does this mean for a to-many relationship? 19 | 20 | /** 21 | * Constructs a new property. 22 | * 23 | * @param entity the associated entity 24 | * @param name the name of the property 25 | * @param type the property type 26 | * @param isRequired if true, property is required to have a value 27 | */ 28 | public Property(Entity entity, String name, Class type, boolean isRequired) { 29 | this.entity = entity; 30 | this.name = name; 31 | this.type = type; 32 | this.isRequired = isRequired; 33 | } 34 | 35 | /** 36 | * Returns the associated entity of this property. 37 | * 38 | * @return the associated entity of this property 39 | */ 40 | public Entity getEntity() { 41 | return entity; 42 | } 43 | 44 | /** 45 | * Returns the type of this property. 46 | * 47 | * @return the type of this property 48 | */ 49 | public Class getType() { 50 | return type; 51 | } 52 | 53 | /** 54 | * Returns the name this property. 55 | * 56 | * @return the name this property 57 | */ 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | /** 63 | * Indicates whether this property is required to have a value or not. 64 | * 65 | * @return true if this property is required to have a value, or false otherwise 66 | */ 67 | public boolean isRequired() { 68 | return isRequired; 69 | } 70 | 71 | /** 72 | * Indicates whether this property represents a relationship. 73 | * 74 | * @return true if this property represents a relationship, or false otherwise 75 | */ 76 | public abstract boolean isRelationship(); 77 | 78 | @Override 79 | public int hashCode() { 80 | final int prime = 31; 81 | int result = 1; 82 | result = prime * result + ((entity == null) ? 0 : entity.hashCode()); 83 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 84 | result = prime * result + ((type == null) ? 0 : type.hashCode()); 85 | return result; 86 | } 87 | 88 | @Override 89 | public boolean equals(Object obj) { 90 | if (this == obj) 91 | return true; 92 | if (obj == null) 93 | return false; 94 | if (getClass() != obj.getClass()) 95 | return false; 96 | Property other = (Property) obj; 97 | if (entity == null) { 98 | if (other.entity != null) 99 | return false; 100 | } else if (!entity.equals(other.entity)) 101 | return false; 102 | if (name == null) { 103 | if (other.name != null) 104 | return false; 105 | } else if (!name.equals(other.name)) 106 | return false; 107 | if (type == null) { 108 | if (other.type != null) 109 | return false; 110 | } else if (!type.equals(other.type)) 111 | return false; 112 | return true; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return getClass().getSimpleName() + "[" 118 | + "name=" + getName() 119 | + ", type=" + getType().getName() 120 | + ", entity=" + getEntity().getName() 121 | + "]"; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/metamodel/Relationship.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.metamodel; 2 | 3 | import com.github.dkharrat.nexusdata.core.ManagedObject; 4 | 5 | /** 6 | * A Relationship is a {@link ManagedObject} {@link Property} that represents a field that stores a to-one or to-many 7 | * relationship. 8 | */ 9 | public class Relationship extends Property { 10 | /** 11 | * Represents the type of the relationship 12 | */ 13 | public enum Type { 14 | TO_ONE, 15 | TO_MANY, 16 | } 17 | 18 | private final Type relationshipType; 19 | private final Entity destinationEntity; 20 | private Relationship inverse; 21 | 22 | //TODO: look into deducing the 'type' param based on relationshipType 23 | /** 24 | * Creates a new Attribute. 25 | * 26 | * @param entity the associated entity 27 | * @param name the name of the property 28 | * @param type the relationship instance type. For to-one relationships, it must be the same as 29 | * {@code destinationEntity} (i.e. type of the related object. For to-many relationships, 30 | * it must be {@link java.util.Set}. 31 | * @param relationshipType the relationship type (to-one or to-many) 32 | * @param destinationEntity the entity of the object(s) this relationship will store 33 | * @param inverse the relationship in the other direction (from the destination entity to this 34 | * relationship's entity 35 | * @param isRequired if true, property is required to have a value 36 | */ 37 | public Relationship(Entity entity, String name, Class type, Type relationshipType, Entity destinationEntity, Relationship inverse, boolean isRequired) { 38 | super(entity, name, type, isRequired); 39 | this.relationshipType = relationshipType; 40 | this.destinationEntity = destinationEntity; 41 | this.inverse = inverse; 42 | } 43 | 44 | /** 45 | * Returns the relationship type. 46 | * 47 | * @return the relationship type 48 | */ 49 | public Type getRelationshipType() { 50 | return relationshipType; 51 | } 52 | 53 | /** 54 | * Indicates whether this relationship is a to-one relationship or not. 55 | * 56 | * @return true if this relationship is a to-one relationship, or false otherwise 57 | */ 58 | public boolean isToOne() { 59 | return relationshipType == Type.TO_ONE; 60 | } 61 | 62 | /** 63 | * Indicates whether this relationship is a to-many relationship or not. 64 | * 65 | * @return true if this relationship is a to-many relationship, or false otherwise 66 | */ 67 | public boolean isToMany() { 68 | return relationshipType == Type.TO_MANY; 69 | } 70 | 71 | /** 72 | * Returns the entity to which this relationship is related to. 73 | * 74 | * @return the entity to which this relationship is related to 75 | */ 76 | public Entity getDestinationEntity() { 77 | return destinationEntity; 78 | } 79 | 80 | /** 81 | * Returns the inverse relationship (i.e the relationship in the other direction). 82 | * 83 | * @return the inverse relationship 84 | */ 85 | public Relationship getInverse() { 86 | return inverse; 87 | } 88 | 89 | void setInverse(Relationship inverse) { 90 | this.inverse = inverse; 91 | } 92 | 93 | @Override 94 | public boolean isRelationship() { 95 | return true; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return super.toString() + "[" 101 | + "destinationEntity=" + (getDestinationEntity() == null ? "" : getDestinationEntity().getName()) 102 | + ", inverseRelationship=" + (getInverse() == null ? "" : getInverse().getName()) 103 | + "]"; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/ComparisonPredicate.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | import com.github.dkharrat.nexusdata.utils.ObjectUtil; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | 8 | public class ComparisonPredicate implements Predicate { 9 | 10 | public enum Operator { 11 | EQUAL, 12 | NOT_EQUAL, 13 | GREATER_THAN, 14 | GREATER_THAN_OR_EQUAL, 15 | LESS_THAN, 16 | LESS_THAN_OR_EQUAL, 17 | } 18 | 19 | private final Expression lhs, rhs; 20 | private final Operator op; 21 | 22 | public ComparisonPredicate(Expression lhs, Operator op, Expression rhs) { 23 | this.lhs = lhs; 24 | this.op = op; 25 | this.rhs = rhs; 26 | } 27 | 28 | public Expression getLhs() { 29 | return lhs; 30 | } 31 | 32 | public Operator getOperator() { 33 | return op; 34 | } 35 | 36 | public Expression getRhs() { 37 | return rhs; 38 | } 39 | 40 | @Override 41 | public T accept(ExpressionVisitor visitor) { 42 | return visitor.visit(this); 43 | } 44 | 45 | @Override 46 | public Boolean evaluate(Object object) { 47 | Object lhsValue = lhs.evaluate(object); 48 | Object rhsValue = rhs.evaluate(object); 49 | 50 | if (op == Operator.EQUAL) { 51 | return ObjectUtil.objectsEqual(lhsValue, rhsValue); 52 | } else if (op == Operator.NOT_EQUAL) { 53 | return !ObjectUtil.objectsEqual(lhsValue, rhsValue); 54 | } 55 | 56 | if (!(lhsValue instanceof Number) || !(rhsValue instanceof Number)) { 57 | throw new IllegalArgumentException("Cannot compare non-Numbers"); 58 | } 59 | Number lhsNumber = (Number)lhsValue; 60 | Number rhsNumber = (Number)rhsValue; 61 | 62 | int comparison = toBigDecimal(lhsNumber).compareTo(toBigDecimal(rhsNumber)); 63 | 64 | switch (op) { 65 | case GREATER_THAN: 66 | return comparison >= 1; 67 | 68 | case GREATER_THAN_OR_EQUAL: 69 | return comparison >= 1 || comparison == 0; 70 | 71 | case LESS_THAN: 72 | return comparison <= -1; 73 | 74 | case LESS_THAN_OR_EQUAL: 75 | return comparison <= -1 || comparison == 0; 76 | 77 | default: 78 | throw new UnsupportedOperationException("Not implemented yet"); 79 | } 80 | } 81 | 82 | private static BigDecimal toBigDecimal(final Number number) { 83 | if(number instanceof BigDecimal) 84 | return (BigDecimal) number; 85 | if(number instanceof BigInteger) 86 | return new BigDecimal((BigInteger) number); 87 | if(number instanceof Byte || number instanceof Short 88 | || number instanceof Integer || number instanceof Long) 89 | return new BigDecimal(number.longValue()); 90 | if(number instanceof Float || number instanceof Double) 91 | return new BigDecimal(number.doubleValue()); 92 | 93 | try { 94 | return new BigDecimal(number.toString()); 95 | } catch(final NumberFormatException e) { 96 | throw new RuntimeException("The given number (\"" + number 97 | + "\" of class " + number.getClass().getName() 98 | + ") does not have a parsable string representation", e); 99 | } 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "(" + lhs + " " + op + " " + rhs + ")"; 105 | } 106 | 107 | @Override 108 | public boolean equals(Object o) { 109 | if (this == o) return true; 110 | if (o == null || getClass() != o.getClass()) return false; 111 | 112 | ComparisonPredicate that = (ComparisonPredicate) o; 113 | 114 | if (!lhs.equals(that.lhs)) return false; 115 | if (op != that.op) return false; 116 | if (!rhs.equals(that.rhs)) return false; 117 | 118 | return true; 119 | } 120 | 121 | @Override 122 | public int hashCode() { 123 | int result = lhs.hashCode(); 124 | result = 31 * result + rhs.hashCode(); 125 | result = 31 * result + op.hashCode(); 126 | return result; 127 | } 128 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/CompoundPredicate.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | 4 | public class CompoundPredicate implements Predicate { 5 | public static enum Operator { 6 | AND, 7 | OR, 8 | } 9 | 10 | private final Predicate lhs, rhs; 11 | private final Operator op; 12 | 13 | public CompoundPredicate(Predicate lhs, Operator op, Predicate rhs) { 14 | this.lhs = lhs; 15 | this.op = op; 16 | this.rhs = rhs; 17 | } 18 | 19 | public Predicate getLhs() { 20 | return lhs; 21 | } 22 | 23 | public Operator getOperator() { 24 | return op; 25 | } 26 | 27 | public Predicate getRhs() { 28 | return rhs; 29 | } 30 | 31 | @Override 32 | public T accept(ExpressionVisitor visitor) { 33 | return visitor.visit(this); 34 | } 35 | 36 | @Override 37 | public Boolean evaluate(Object object) { 38 | boolean lhsValue = lhs.evaluate(object); 39 | boolean rhsValue = rhs.evaluate(object); 40 | 41 | switch (op) { 42 | case AND: 43 | return lhsValue && rhsValue; 44 | case OR: 45 | return lhsValue || rhsValue; 46 | default: 47 | throw new UnsupportedOperationException("Unsupported compound operator: " + op); 48 | } 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "(" + lhs + " " + op + " " + rhs + ")"; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (this == o) return true; 59 | if (o == null || getClass() != o.getClass()) return false; 60 | 61 | CompoundPredicate that = (CompoundPredicate) o; 62 | 63 | if (!lhs.equals(that.lhs)) return false; 64 | if (op != that.op) return false; 65 | if (!rhs.equals(that.rhs)) return false; 66 | 67 | return true; 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | int result = lhs.hashCode(); 73 | result = 31 * result + rhs.hashCode(); 74 | result = 31 * result + op.hashCode(); 75 | return result; 76 | } 77 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/ConstantExpression.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | public class ConstantExpression implements Expression { 4 | 5 | private final T value; 6 | 7 | public ConstantExpression(T value) { 8 | this.value = value; 9 | } 10 | 11 | public T getValue() { 12 | return value; 13 | } 14 | 15 | @Override 16 | public T evaluate(Object object) { 17 | return getValue(); 18 | } 19 | 20 | @Override 21 | public T accept(ExpressionVisitor visitor) { 22 | return visitor.visit(this); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return value == null ? null : value.toString(); 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | 35 | ConstantExpression that = (ConstantExpression) o; 36 | 37 | if (value != null ? !value.equals(that.value) : that.value != null) return false; 38 | 39 | return true; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return value != null ? value.hashCode() : 0; 45 | } 46 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/Expression.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | 4 | public interface Expression { 5 | 6 | public V accept(ExpressionVisitor visitor); 7 | public T evaluate(Object object); 8 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/ExpressionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | 4 | public class ExpressionBuilder { 5 | private final Expression curExpression; 6 | 7 | ExpressionBuilder(Expression curExp) { 8 | curExpression = curExp; 9 | } 10 | 11 | public Expression getExpression() { 12 | return curExpression; 13 | } 14 | 15 | public static PredicateBuilder constant(T value) { 16 | return new PredicateBuilder(new ConstantExpression(value)); 17 | } 18 | 19 | public static PredicateBuilder field(String fieldPath) { 20 | return new PredicateBuilder(new FieldPathExpression(fieldPath)); 21 | } 22 | 23 | public static PredicateBuilder self() { 24 | return new PredicateBuilder(new ThisExpression()); 25 | } 26 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/ExpressionVisitor.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | public interface ExpressionVisitor { 4 | public T visit(ConstantExpression expression); 5 | public T visit(FieldPathExpression expression); 6 | public T visit(ThisExpression expression); 7 | public T visit(CompoundPredicate predicate); 8 | public T visit(ComparisonPredicate predicate); 9 | public T visit(NotPredicate predicate); 10 | } 11 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/FieldPathExpression.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import com.github.dkharrat.nexusdata.core.ManagedObject; 6 | 7 | public class FieldPathExpression implements Expression { 8 | 9 | private final String fieldPath; 10 | 11 | public FieldPathExpression(String fieldPath) { 12 | this.fieldPath = fieldPath; 13 | } 14 | 15 | public String getFieldPath() { 16 | return fieldPath; 17 | } 18 | 19 | @Override 20 | public T accept(ExpressionVisitor visitor) { 21 | return visitor.visit(this); 22 | } 23 | 24 | @Override 25 | public Object evaluate(Object object) { 26 | try { 27 | if (object instanceof ManagedObject) { 28 | return ((ManagedObject)object).getValue(fieldPath); 29 | } else { 30 | Field field = object.getClass().getDeclaredField(fieldPath); 31 | field.setAccessible(true); 32 | return field.get(object); 33 | } 34 | } catch (Exception e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return fieldPath; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | 49 | FieldPathExpression that = (FieldPathExpression) o; 50 | 51 | if (!fieldPath.equals(that.fieldPath)) return false; 52 | 53 | return true; 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return fieldPath.hashCode(); 59 | } 60 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/NotPredicate.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | public class NotPredicate implements Predicate { 4 | private final Predicate predicate; 5 | 6 | public NotPredicate(Predicate predicate) { 7 | this.predicate = predicate; 8 | } 9 | 10 | public Predicate getPredicate() { 11 | return predicate; 12 | } 13 | 14 | @Override 15 | public T accept(ExpressionVisitor visitor) { 16 | return visitor.visit(this); 17 | } 18 | 19 | @Override 20 | public Boolean evaluate(Object object) { 21 | return !predicate.evaluate(object); 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | 29 | NotPredicate that = (NotPredicate) o; 30 | 31 | if (!predicate.equals(that.predicate)) return false; 32 | 33 | return true; 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return ~predicate.hashCode(); 39 | } 40 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | 4 | public interface Predicate extends Expression { 5 | public Boolean evaluate(Object object); 6 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/PredicateBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | 4 | import com.github.dkharrat.nexusdata.predicate.parser.PredicateParser; 5 | 6 | public class PredicateBuilder { 7 | 8 | private final Predicate curPredicate; 9 | private final Expression curExpression; 10 | 11 | PredicateBuilder(Predicate curPredicate) { 12 | this.curPredicate = curPredicate; 13 | this.curExpression = null; 14 | } 15 | 16 | PredicateBuilder(Expression curExpression) { 17 | this.curExpression = curExpression; 18 | this.curPredicate = null; 19 | } 20 | 21 | public Predicate getPredicate() { 22 | return curPredicate; 23 | } 24 | 25 | public static Predicate parse(String expr) { 26 | PredicateParser parser = new PredicateParser(expr); 27 | return parser.parse(); 28 | } 29 | 30 | Expression getExpression() { 31 | return curExpression; 32 | } 33 | 34 | public PredicateBuilder gt(Expression rhs) { 35 | return new PredicateBuilder(new ComparisonPredicate(curExpression, ComparisonPredicate.Operator.GREATER_THAN, rhs)); 36 | } 37 | 38 | public PredicateBuilder gt(ExpressionBuilder rhs) { 39 | return gt(rhs.getExpression()); 40 | } 41 | 42 | public PredicateBuilder gt(T value) { 43 | return gt(new ConstantExpression(value)); 44 | } 45 | 46 | public PredicateBuilder lt(Expression rhs) { 47 | return new PredicateBuilder(new ComparisonPredicate(curExpression, ComparisonPredicate.Operator.LESS_THAN, rhs)); 48 | } 49 | 50 | public PredicateBuilder lt(ExpressionBuilder rhs) { 51 | return lt(rhs.getExpression()); 52 | } 53 | 54 | public PredicateBuilder lt(T value) { 55 | return lt(new ConstantExpression(value)); 56 | } 57 | 58 | public PredicateBuilder eq(Expression rhs) { 59 | return new PredicateBuilder(new ComparisonPredicate(curExpression, ComparisonPredicate.Operator.EQUAL, rhs)); 60 | } 61 | 62 | public PredicateBuilder eq(ExpressionBuilder rhs) { 63 | return eq(rhs.getExpression()); 64 | } 65 | 66 | public PredicateBuilder eq(T value) { 67 | return eq(new ConstantExpression(value)); 68 | } 69 | 70 | public PredicateBuilder notEq(Expression rhs) { 71 | return new PredicateBuilder(new ComparisonPredicate(curExpression, ComparisonPredicate.Operator.NOT_EQUAL, rhs)); 72 | } 73 | 74 | public PredicateBuilder notEq(ExpressionBuilder rhs) { 75 | return notEq(rhs.getExpression()); 76 | } 77 | 78 | public PredicateBuilder notEq(T value) { 79 | return notEq(new ConstantExpression(value)); 80 | } 81 | 82 | public PredicateBuilder isNull() { 83 | return eq(new ConstantExpression(null)); 84 | } 85 | 86 | public PredicateBuilder isNotNull() { 87 | return notEq(new ConstantExpression(null)); 88 | } 89 | 90 | public PredicateBuilder and(PredicateBuilder rhs) { 91 | return new PredicateBuilder(new CompoundPredicate(curPredicate, CompoundPredicate.Operator.AND, rhs.getPredicate())); 92 | } 93 | 94 | public PredicateBuilder or(PredicateBuilder rhs) { 95 | return new PredicateBuilder(new CompoundPredicate(curPredicate, CompoundPredicate.Operator.OR, rhs.getPredicate())); 96 | } 97 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/ThisExpression.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate; 2 | 3 | public class ThisExpression implements Expression { 4 | 5 | @Override 6 | public Object evaluate(Object object) { 7 | return object; 8 | } 9 | 10 | @Override 11 | public T accept(ExpressionVisitor visitor) { 12 | return visitor.visit(this); 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "this"; 18 | } 19 | } -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/ComparisonParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import com.github.dkharrat.nexusdata.predicate.ComparisonPredicate; 4 | import static com.github.dkharrat.nexusdata.predicate.ComparisonPredicate.Operator; 5 | import com.github.dkharrat.nexusdata.predicate.Expression; 6 | 7 | import static com.github.dkharrat.nexusdata.predicate.parser.PredicateParser.TokenType; 8 | 9 | public class ComparisonParselet implements InfixParselet> { 10 | private final Operator operator; 11 | private final int precedence; 12 | 13 | ComparisonParselet(Operator operator, int precedence) { 14 | this.operator = operator; 15 | this.precedence = precedence; 16 | } 17 | 18 | public Expression parse(Parser> parser, Expression left, Token token) { 19 | 20 | Expression right = parser.parse(getPrecedence() - 1); 21 | 22 | return new ComparisonPredicate(left, operator, right); 23 | } 24 | 25 | public int getPrecedence() { 26 | return precedence; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/ConstantParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import com.github.dkharrat.nexusdata.predicate.ConstantExpression; 4 | import com.github.dkharrat.nexusdata.predicate.Expression; 5 | 6 | import static com.github.dkharrat.nexusdata.predicate.parser.PredicateParser.TokenType; 7 | 8 | public class ConstantParselet implements PrefixParselet> { 9 | @Override 10 | public Expression parse(Parser> parser, Token token) { 11 | 12 | String valueStr = token.getText(); 13 | Object value; 14 | 15 | if (valueStr.equalsIgnoreCase("true")) { 16 | value = true; 17 | } else if (valueStr.equalsIgnoreCase("false")) { 18 | value = false; 19 | } else if (valueStr.equalsIgnoreCase("null")) { 20 | value = null; 21 | } else if (valueStr.startsWith("\"")) { 22 | value = valueStr.substring(1,valueStr.length()-1); // remove quotes from string 23 | } else { 24 | value = Integer.parseInt(valueStr); 25 | } 26 | 27 | return new ConstantExpression(value); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/ExpressionNode.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | public interface ExpressionNode { 4 | void print(StringBuilder builder); 5 | } 6 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/GroupParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import com.github.dkharrat.nexusdata.predicate.Expression; 4 | 5 | import static com.github.dkharrat.nexusdata.predicate.parser.PredicateParser.TokenType; 6 | 7 | public class GroupParselet implements PrefixParselet> { 8 | @Override 9 | public Expression parse(Parser> parser, Token token) { 10 | Expression predicate = parser.parse(); 11 | parser.consume(TokenType.CLOSE_PAREN); 12 | return predicate; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/InfixParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | interface InfixParselet { 4 | V parse(Parser parser, V left, Token token); 5 | int getPrecedence(); 6 | } 7 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/Lexer.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import java.util.Iterator; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.regex.Matcher; 7 | 8 | class Lexer implements Iterator> { 9 | 10 | private final LexerGrammar grammar; 11 | private final List> tokens = new LinkedList>(); 12 | private final String text; 13 | private Iterator> tokenIterator = null; 14 | 15 | Lexer(LexerGrammar grammar, String text) { 16 | this.grammar = grammar; 17 | this.text = text; 18 | } 19 | 20 | private void tokenize() { 21 | //TODO: make it stream-based, instead of parsing everything 22 | 23 | String s = text; 24 | tokens.clear(); 25 | 26 | boolean foundToken = false; 27 | while(!s.equals("")) { 28 | for (LexerGrammar.TokenRule tokenDescriptor : grammar.getRules()) { 29 | Matcher matcher = tokenDescriptor.getPattern().matcher(s); 30 | if (matcher.find()) { 31 | foundToken = true; 32 | String tokenStr = matcher.group().trim(); 33 | tokens.add(new Token(tokenDescriptor.getType(), tokenStr)); 34 | 35 | s = matcher.replaceFirst(""); 36 | break; 37 | } 38 | } 39 | 40 | if (!foundToken) { 41 | throw new ParseException("Unexpected token in expression near: " + s); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | public boolean hasNext() { 48 | return true; 49 | } 50 | 51 | @Override 52 | public Token next() { 53 | if (tokenIterator == null) { 54 | tokenize(); 55 | tokenIterator = tokens.iterator(); 56 | } 57 | 58 | if (tokenIterator.hasNext()) { 59 | return tokenIterator.next(); 60 | } else { 61 | return new Token(grammar.getEofToken(), null); 62 | } 63 | } 64 | 65 | @Override 66 | public void remove() { 67 | throw new UnsupportedOperationException(); 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/LexerGrammar.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.regex.Pattern; 6 | 7 | class LexerGrammar { 8 | class TokenRule { 9 | private final Pattern pattern; 10 | private final T type; 11 | 12 | TokenRule(Pattern pattern, T type) { 13 | this.pattern = pattern; 14 | this.type = type; 15 | } 16 | 17 | Pattern getPattern() { 18 | return pattern; 19 | } 20 | 21 | T getType() { 22 | return type; 23 | } 24 | } 25 | 26 | private final List tokenRules = new LinkedList(); 27 | private final T eofToken; 28 | 29 | public LexerGrammar(T eofToken) { 30 | this.eofToken = eofToken; 31 | } 32 | 33 | void add(String regex, T tokenType) { 34 | tokenRules.add(new TokenRule(Pattern.compile("^\\s*(" + regex + ")\\s*"), tokenType)); 35 | } 36 | 37 | List getRules() { 38 | return tokenRules; 39 | } 40 | 41 | T getEofToken() { 42 | return eofToken; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/LogicalParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import com.github.dkharrat.nexusdata.predicate.CompoundPredicate; 4 | import com.github.dkharrat.nexusdata.predicate.Expression; 5 | import com.github.dkharrat.nexusdata.predicate.Predicate; 6 | import static com.github.dkharrat.nexusdata.predicate.CompoundPredicate.Operator; 7 | import static com.github.dkharrat.nexusdata.predicate.parser.PredicateParser.TokenType; 8 | 9 | public class LogicalParselet implements InfixParselet> { 10 | 11 | private final Operator operator; 12 | private final int precedence; 13 | 14 | LogicalParselet(Operator operator, int precedence) { 15 | this.operator = operator; 16 | this.precedence = precedence; 17 | } 18 | 19 | public Expression parse(Parser> parser, Expression left, Token token) { 20 | 21 | if (!(left instanceof Predicate)) { 22 | throw new ParseException("Expected a predicate for left-hand side, but got an expression"); 23 | } 24 | 25 | Expression right = parser.parse(getPrecedence() - 1); 26 | 27 | if (!(right instanceof Predicate)) { 28 | throw new ParseException("Expected a predicate for right-hand side, but got an expression"); 29 | } 30 | 31 | return new CompoundPredicate((Predicate)left, operator, (Predicate)right); 32 | } 33 | 34 | public int getPrecedence() { 35 | return precedence; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/NameParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import com.github.dkharrat.nexusdata.predicate.Expression; 4 | import com.github.dkharrat.nexusdata.predicate.FieldPathExpression; 5 | import static com.github.dkharrat.nexusdata.predicate.parser.PredicateParser.TokenType; 6 | 7 | class NameParselet implements PrefixParselet> { 8 | public Expression parse(Parser> parser, Token token) { 9 | return new FieldPathExpression(token.getText()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/ParseException.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | public class ParseException extends RuntimeException { 4 | ParseException(String msg) { 5 | super(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Generic and extensible parser for a string. It works by registering a "Parselet" that defines parsing 7 | * behavior for a specific token. 8 | *

9 | * Implementation is based on Bob Nystrom's post. 10 | * 11 | * @param The token type 12 | * @param The return type of the parser 13 | */ 14 | class Parser { 15 | private final Map> prefixParselets = new HashMap>(); 16 | private final Map> infixParselets = new HashMap>(); 17 | private final Iterator> tokens; 18 | private final List> read = new ArrayList>(); 19 | 20 | public Parser(Iterator> tokens) { 21 | this.tokens = tokens; 22 | } 23 | 24 | public V parse(int precedence) { 25 | Token token = consume(); 26 | PrefixParselet prefix = prefixParselets.get(token.getType()); 27 | 28 | if (prefix == null) { 29 | throw new ParseException("Could not parse \"" + token.getText() + "\"."); 30 | } 31 | 32 | V left = prefix.parse(this, token); 33 | 34 | while (precedence < getPrecedence()) { 35 | token = consume(); 36 | 37 | InfixParselet infix = infixParselets.get(token.getType()); 38 | left = infix.parse(this, left, token); 39 | } 40 | 41 | return left; 42 | } 43 | 44 | public V parse() { 45 | return parse(0); 46 | } 47 | 48 | public void registerParslets(T tokenType, PrefixParselet prefixParselet) { 49 | prefixParselets.put(tokenType, prefixParselet); 50 | } 51 | 52 | public void registerParslets(T tokenType, InfixParselet infixParselet) { 53 | infixParselets.put(tokenType, infixParselet); 54 | } 55 | 56 | public Token consume() { 57 | lookAhead(0); 58 | return read.remove(0); 59 | } 60 | 61 | public Token consume(T expectedToken) { 62 | Token token = lookAhead(0); 63 | if (token.getType() != expectedToken) { 64 | throw new ParseException("Expected token " + expectedToken + ", but found " + token); 65 | } 66 | return consume(); 67 | } 68 | 69 | private Token lookAhead(int distance) { 70 | while (read.size() <= distance) { 71 | read.add(tokens.next()); 72 | } 73 | return read.get(distance); 74 | } 75 | 76 | private int getPrecedence() { 77 | InfixParselet parser = infixParselets.get(lookAhead(0).getType()); 78 | if (parser != null) return parser.getPrecedence(); 79 | 80 | return 0; 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/PredicateParser.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | import com.github.dkharrat.nexusdata.predicate.ComparisonPredicate; 4 | import com.github.dkharrat.nexusdata.predicate.CompoundPredicate; 5 | import com.github.dkharrat.nexusdata.predicate.Expression; 6 | import com.github.dkharrat.nexusdata.predicate.Predicate; 7 | 8 | public class PredicateParser { 9 | 10 | public static enum TokenType { 11 | AND, 12 | OR, 13 | OPEN_PAREN, 14 | CLOSE_PAREN, 15 | EQUAL, 16 | GREATER_THAN_OR_EQUAL, 17 | LESS_THAN_OR_EQUAL, 18 | NOT_EQUAL, 19 | GREATER_THAN, 20 | LESS_THAN, 21 | FIELD_NAME, 22 | CONSTANT, 23 | EOF 24 | } 25 | 26 | public static interface Precedence { 27 | public static final int OR = 1; 28 | public static final int AND = 2; 29 | public static final int EQUALITY = 3; 30 | public static final int INEQUALITY = 4; 31 | public static final int NOT = 5; 32 | public static final int PREFIX = 6; 33 | public static final int POSTFIX = 7; 34 | } 35 | 36 | private final Parser> parser; 37 | private final static LexerGrammar lexerGrammar = new LexerGrammar(TokenType.EOF); 38 | static { 39 | lexerGrammar.add("\\(", TokenType.OPEN_PAREN); 40 | lexerGrammar.add("\\)", TokenType.CLOSE_PAREN); 41 | lexerGrammar.add("&&", TokenType.AND); 42 | lexerGrammar.add("\\|\\|", TokenType.OR); 43 | lexerGrammar.add("==", TokenType.EQUAL); 44 | lexerGrammar.add("!=", TokenType.NOT_EQUAL); 45 | lexerGrammar.add(">=", TokenType.GREATER_THAN_OR_EQUAL); 46 | lexerGrammar.add("<=", TokenType.LESS_THAN_OR_EQUAL); 47 | lexerGrammar.add(">", TokenType.GREATER_THAN); 48 | lexerGrammar.add("<", TokenType.LESS_THAN); 49 | lexerGrammar.add("(\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\")|\\d+|true|false|null", TokenType.CONSTANT); 50 | lexerGrammar.add("[a-zA-Z][a-zA-Z0-9_]*", TokenType.FIELD_NAME); 51 | } 52 | 53 | public PredicateParser(String text) { 54 | Lexer tokenizer = new Lexer(lexerGrammar, text); 55 | parser = new Parser>(tokenizer); 56 | parser.registerParslets(TokenType.OPEN_PAREN, new GroupParselet()); 57 | parser.registerParslets(TokenType.EQUAL, new ComparisonParselet(ComparisonPredicate.Operator.EQUAL, Precedence.EQUALITY)); 58 | parser.registerParslets(TokenType.NOT_EQUAL, new ComparisonParselet(ComparisonPredicate.Operator.NOT_EQUAL, Precedence.EQUALITY)); 59 | parser.registerParslets(TokenType.GREATER_THAN, new ComparisonParselet(ComparisonPredicate.Operator.GREATER_THAN, Precedence.INEQUALITY)); 60 | parser.registerParslets(TokenType.GREATER_THAN_OR_EQUAL, new ComparisonParselet(ComparisonPredicate.Operator.GREATER_THAN_OR_EQUAL, Precedence.INEQUALITY)); 61 | parser.registerParslets(TokenType.LESS_THAN, new ComparisonParselet(ComparisonPredicate.Operator.LESS_THAN, Precedence.INEQUALITY)); 62 | parser.registerParslets(TokenType.LESS_THAN_OR_EQUAL, new ComparisonParselet(ComparisonPredicate.Operator.LESS_THAN_OR_EQUAL, Precedence.INEQUALITY)); 63 | parser.registerParslets(TokenType.AND, new LogicalParselet(CompoundPredicate.Operator.AND, Precedence.AND)); 64 | parser.registerParslets(TokenType.OR, new LogicalParselet(CompoundPredicate.Operator.OR, Precedence.OR)); 65 | parser.registerParslets(TokenType.CONSTANT, new ConstantParselet()); 66 | parser.registerParslets(TokenType.FIELD_NAME, new NameParselet()); 67 | } 68 | 69 | public Predicate parse() { 70 | return (Predicate)parser.parse(); 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/PrefixParselet.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | interface PrefixParselet { 4 | V parse(Parser parser, Token token); 5 | } 6 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/predicate/parser/Token.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.predicate.parser; 2 | 3 | class Token { 4 | private final TokenType type; 5 | private final String text; 6 | 7 | Token(TokenType type, String value) { 8 | this.type = type; 9 | this.text = value; 10 | } 11 | 12 | public TokenType getType() { 13 | return type; 14 | } 15 | 16 | public String getText() { 17 | return text; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/store/InMemoryPersistentStore.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.store; 2 | 3 | import java.io.File; 4 | 5 | import com.github.dkharrat.nexusdata.core.AtomicStore; 6 | import com.github.dkharrat.nexusdata.core.ManagedObject; 7 | 8 | 9 | public class InMemoryPersistentStore extends AtomicStore { 10 | 11 | long lastUnusedId = 1; 12 | 13 | public InMemoryPersistentStore() { 14 | super((File)null); 15 | } 16 | 17 | @Override 18 | public void load() { 19 | // Nothing to load 20 | } 21 | 22 | @Override 23 | public void save() { 24 | // Nothing to save 25 | } 26 | 27 | @Override 28 | public Object createReferenceObjectForManagedObject(ManagedObject object) { 29 | return lastUnusedId++; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/store/Utils.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.store; 2 | 3 | import com.github.dkharrat.nexusdata.metamodel.Entity; 4 | import com.github.dkharrat.nexusdata.metamodel.Property; 5 | 6 | import java.util.*; 7 | 8 | class Utils { 9 | static Collection> getAllChildEntities(Entity entity, Collection> childEntities) { 10 | childEntities.addAll(entity.getSubEntities()); 11 | for (Entity subEntity : entity.getSubEntities()) { 12 | getAllChildEntities(subEntity, childEntities); 13 | } 14 | return childEntities; 15 | } 16 | 17 | static Set getPropertiesOfEntityAndItsChildren(Entity entity) { 18 | Set properties = new LinkedHashSet<>(); 19 | 20 | properties.addAll(entity.getProperties()); 21 | Collection> childEntities = getAllChildEntities(entity, new ArrayList>()); 22 | for (Entity childEntity : childEntities) { 23 | properties.addAll(childEntity.getProperties()); 24 | } 25 | 26 | return properties; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/utils/ObjectUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.utils; 2 | 3 | public class ObjectUtil { 4 | 5 | public static boolean objectsEqual(Object a, Object b) { 6 | return a == b || (a != null && a.equals(b)); 7 | } 8 | 9 | public static Comparable toComparable(Object object) { 10 | if (object == null) { 11 | return null; 12 | } else if (object instanceof Comparable) { 13 | return (Comparable) object; 14 | } else if (object instanceof StringBuffer) { 15 | return object.toString(); 16 | } else if (object instanceof char[]) { 17 | return new String((char[]) object); 18 | } else { 19 | throw new ClassCastException("Could not get a valid Comparable instance for class:" + object.getClass().getName()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/utils/StreamUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.utils; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | public class StreamUtil { 8 | public static String getStringFromStream(InputStream in, int maxLength) throws IOException { 9 | if (maxLength == 0) { 10 | maxLength = Integer.MAX_VALUE; 11 | } 12 | 13 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 14 | byte[] buffer = new byte[1024]; 15 | int lengthSoFar = 0; 16 | int curLength = 0; 17 | while ((curLength = in.read(buffer)) != -1 && lengthSoFar <= maxLength) { 18 | baos.write(buffer, 0, Math.min(curLength, maxLength - lengthSoFar)); 19 | lengthSoFar += curLength; 20 | } 21 | return new String(baos.toByteArray()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.utils; 2 | 3 | import java.text.ParseException; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | public class StringUtil { 10 | public static boolean isBlank(String s) { 11 | return s == null || s.length() == 0; 12 | } 13 | 14 | public static String join(Collection list, String separator, boolean ignoreNull) { 15 | if (list.isEmpty()) 16 | return ""; 17 | 18 | StringBuilder b = new StringBuilder(); 19 | for (Object item : list) { 20 | if (!ignoreNull || item != null) { 21 | b.append(separator).append(item); 22 | } 23 | } 24 | 25 | if (b.length() == 0) { 26 | return ""; 27 | } 28 | 29 | return b.toString().substring(separator.length()); 30 | } 31 | 32 | public static String join(Collection list, String separator) { 33 | return join(list, separator, false); 34 | } 35 | 36 | public static String join(T[] array, String separator, boolean ignoreNull) { 37 | return join(Arrays.asList(array), separator, ignoreNull); 38 | } 39 | 40 | public static String join(T[] array, String separator) { 41 | return join(array, separator, false); 42 | } 43 | 44 | @SuppressWarnings("unchecked") 45 | public static Object convertStringValueToType(String value, Class propType) throws ParseException, NumberFormatException, IllegalArgumentException { 46 | Object result; 47 | if (propType.isAssignableFrom(Integer.class) || propType.isAssignableFrom(int.class)) { 48 | result = Integer.parseInt(value); 49 | } else if (propType.isAssignableFrom(Long.class) || propType.isAssignableFrom(long.class)) { 50 | result = Long.parseLong(value); 51 | } else if (propType.isAssignableFrom(String.class)) { 52 | result = value; 53 | } else if (propType.isAssignableFrom(Boolean.class) || propType.isAssignableFrom(boolean.class)) { 54 | result = Boolean.valueOf(value); 55 | } else if (propType.isAssignableFrom(Float.class) || propType.isAssignableFrom(float.class)) { 56 | result = Float.valueOf(value); 57 | } else if (propType.isAssignableFrom(Double.class) || propType.isAssignableFrom(double.class)) { 58 | result = Double.valueOf(value); 59 | } else if (Enum.class.isAssignableFrom(propType)) { 60 | if (value != null) { 61 | result = Enum.valueOf((Class)propType, value); 62 | } else { 63 | result = null; 64 | } 65 | } else if (propType.isAssignableFrom(Date.class)) { 66 | if (value != null) { 67 | result = DateUtil.parse(DateUtil.ISO8601_NO_TIMEZONE, value); 68 | } else { 69 | result = null; 70 | } 71 | } else { 72 | throw new UnsupportedOperationException("Unsupported property type " + propType + " for value " + value); 73 | } 74 | 75 | return result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /nexusdata/src/main/java/com/github/dkharrat/nexusdata/utils/android/CursorUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.dkharrat.nexusdata.utils.android; 2 | 3 | import android.database.Cursor; 4 | 5 | public class CursorUtil { 6 | 7 | public static short getShort(Cursor c, String columnName) { 8 | return c.getShort(c.getColumnIndex(columnName)); 9 | } 10 | 11 | public static int getInt(Cursor c, String columnName) { 12 | return c.getInt(c.getColumnIndex(columnName)); 13 | } 14 | 15 | public static long getLong(Cursor c, String columnName) { 16 | return c.getLong(c.getColumnIndex(columnName)); 17 | } 18 | 19 | public static String getString(Cursor c, String columnName) { 20 | return c.getString(c.getColumnIndex(columnName)); 21 | } 22 | 23 | public static boolean getBoolean(Cursor c, String columnName) { 24 | return c.getInt(c.getColumnIndex(columnName)) != 0; 25 | } 26 | 27 | public static float getFloat(Cursor c, String columnName) { 28 | return c.getFloat(c.getColumnIndex(columnName)); 29 | } 30 | 31 | public static double getDouble(Cursor c, String columnName) { 32 | return c.getDouble(c.getColumnIndex(columnName)); 33 | } 34 | 35 | public static boolean isNull(Cursor c, String columnName) { 36 | return c.isNull(c.getColumnIndex(columnName)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/todo/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.0.0' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.application' 12 | 13 | repositories { 14 | jcenter() 15 | } 16 | 17 | dependencies { 18 | compile project(':nexusdata') 19 | compile 'com.github.tony19:logback-android-core:1.1.1-3' 20 | compile 'com.github.tony19:logback-android-classic:1.1.1-3' 21 | } 22 | 23 | android { 24 | compileSdkVersion 21 25 | buildToolsVersion "21.1.2" 26 | 27 | defaultConfig { 28 | applicationId "com.github.dkharrat.nexusdata.sample.todo" 29 | minSdkVersion 10 30 | targetSdkVersion 21 31 | versionCode 1 32 | versionName "1.0" 33 | } 34 | 35 | lintOptions { 36 | abortOnError false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/todo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/todo/src/main/assets/todo.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaVersion": 1, 3 | "model": { 4 | "name": "Todo", 5 | "version": 3, 6 | "packageName": "org.example.todo", 7 | "entities": [{ 8 | "name": "Task", 9 | "enums": [{ 10 | "name": "Priority", 11 | "values": ["HIGH", "MEDIUM", "LOW"] 12 | }], 13 | "attributes": [{ 14 | "name": "title", 15 | "type": "String" 16 | }, { 17 | "name": "notes", 18 | "type": "String" 19 | }, { 20 | "name": "dueBy", 21 | "type": "Date" 22 | }, { 23 | "name": "completed", 24 | "type": "Bool", 25 | "required": true, 26 | "default": false 27 | }, { 28 | "name": "priority", 29 | "type": "Priority" 30 | }], 31 | "relationships": [{ 32 | "name": "assignedTo", 33 | "destinationEntity": "User", 34 | "inverseName": "tasks" 35 | }] 36 | }, { 37 | "name": "User", 38 | "attributes": [{ 39 | "name": "name", 40 | "type": "String" 41 | }], 42 | "relationships": [{ 43 | "name": "tasks", 44 | "destinationEntity": "Task", 45 | "inverseName": "assignedTo", 46 | "toMany": true 47 | }] 48 | }] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.todo; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.graphics.Paint; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.view.*; 11 | import android.widget.*; 12 | import com.github.dkharrat.nexusdata.core.FetchRequest; 13 | import com.github.dkharrat.nexusdata.core.ObjectContext; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | public class MainActivity extends Activity { 19 | 20 | private enum DisplayMode { 21 | TODO, 22 | COMPLETED 23 | } 24 | 25 | private ListView listView; 26 | private TaskListAdapter listAdapter; 27 | 28 | private static final int ADD_TASK = 1; 29 | 30 | private DisplayMode displayMode = DisplayMode.TODO; 31 | 32 | @Override 33 | public void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.main); 36 | 37 | getActionBar().setDisplayShowTitleEnabled(false); 38 | getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 39 | getActionBar().setListNavigationCallbacks(new ArrayAdapter(this, android.R.layout.simple_list_item_1, Arrays.asList("Todo", "Completed")), new ActionBar.OnNavigationListener() { 40 | @Override 41 | public boolean onNavigationItemSelected(int itemPosition, long itemId) { 42 | if (itemPosition == 0) { 43 | displayMode = DisplayMode.TODO; 44 | } else { 45 | displayMode = DisplayMode.COMPLETED; 46 | } 47 | refreshUI(); 48 | return true; 49 | } 50 | }); 51 | 52 | listView = (ListView)findViewById(R.id.todo_tasks_list); 53 | 54 | refreshUI(); 55 | } 56 | 57 | @Override 58 | public void onResume() { 59 | super.onResume(); 60 | 61 | refreshUI(); 62 | } 63 | 64 | @Override 65 | public boolean onCreateOptionsMenu(Menu menu) { 66 | menu.add(Menu.NONE, ADD_TASK, Menu.NONE, "Add") 67 | .setIcon(android.R.drawable.ic_menu_add) 68 | .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS); 69 | 70 | return true; 71 | } 72 | 73 | @Override 74 | public boolean onOptionsItemSelected(MenuItem item) { 75 | boolean result = super.onOptionsItemSelected(item); 76 | 77 | switch (item.getItemId()) { 78 | case ADD_TASK: { 79 | Intent intent = new Intent(this, NewTaskActivity.class); 80 | startActivity(intent); 81 | 82 | break; 83 | } 84 | } 85 | 86 | return result; 87 | } 88 | 89 | private void refreshUI() { 90 | ObjectContext ctx = TodoApp.getMainObjectContext(); 91 | 92 | boolean displayCompleted = (displayMode == DisplayMode.COMPLETED); 93 | FetchRequest fetchRequest = ctx.newFetchRequestBuilder(Task.class) 94 | .predicate("completed == "+displayCompleted) 95 | .build(); 96 | List tasks = ctx.executeFetchOperation(fetchRequest); 97 | 98 | if (listAdapter == null) { 99 | listAdapter = new TaskListAdapter(this, tasks); 100 | listView.setAdapter(listAdapter); 101 | } else { 102 | listAdapter.clear(); 103 | listAdapter.addAll(tasks); 104 | listAdapter.notifyDataSetChanged(); 105 | } 106 | } 107 | 108 | public class TaskListAdapter extends ArrayAdapter { 109 | 110 | Handler handler = new Handler(); 111 | 112 | public TaskListAdapter(Context context, List entries) { 113 | super(context, 0, entries); 114 | } 115 | 116 | private void setStrikeThroughView(TextView textView, boolean strikeThrough) { 117 | if (strikeThrough) { 118 | textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); 119 | } else { 120 | textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); 121 | } 122 | } 123 | 124 | @Override 125 | public View getView(final int position, final View convertView, final ViewGroup parent) { 126 | final Task task = getItem(position); 127 | 128 | final LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 129 | 130 | final View view = inflater.inflate(R.layout.list_item_with_detail, null); 131 | 132 | final TextView titleView = (TextView)view.findViewById(R.id.list_item_title); 133 | final TextView detailsView = (TextView)view.findViewById(R.id.list_item_detail); 134 | final CheckBox completedCheckbox = (CheckBox)view.findViewById(R.id.checkbox_completed); 135 | completedCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 136 | @Override 137 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 138 | task.setCompleted(isChecked); 139 | if (task.isCompleted()) { 140 | setStrikeThroughView(titleView, true); 141 | setStrikeThroughView(detailsView, true); 142 | } else { 143 | setStrikeThroughView(titleView, false); 144 | setStrikeThroughView(detailsView, false); 145 | } 146 | TodoApp.getMainObjectContext().save(); 147 | 148 | if (displayMode == DisplayMode.COMPLETED && !task.isCompleted() || 149 | displayMode == DisplayMode.TODO && task.isCompleted()) { 150 | handler.postDelayed(new Runnable() { 151 | public void run() { 152 | listAdapter.remove(task); 153 | listAdapter.notifyDataSetChanged(); 154 | } 155 | }, 1500); 156 | } 157 | } 158 | }); 159 | 160 | titleView.setText(task.getTitle()); 161 | detailsView.setText(task.getNotes()); 162 | completedCheckbox.setChecked(task.isCompleted()); 163 | 164 | if (task.isCompleted()) { 165 | setStrikeThroughView(titleView, true); 166 | setStrikeThroughView(detailsView, true); 167 | } 168 | 169 | return view; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/NewTaskActivity.java: -------------------------------------------------------------------------------- 1 | package org.example.todo; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.view.Menu; 7 | import android.view.MenuInflater; 8 | import android.view.MenuItem; 9 | import android.widget.EditText; 10 | import ch.qos.logback.classic.Logger; 11 | import com.github.dkharrat.nexusdata.core.ObjectContext; 12 | 13 | public class NewTaskActivity extends Activity { 14 | 15 | EditText titleText, notesText; 16 | 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | 20 | setContentView(R.layout.new_task); 21 | 22 | titleText = (EditText)findViewById(R.id.title); 23 | notesText = (EditText)findViewById(R.id.notes); 24 | } 25 | 26 | @Override 27 | public boolean onCreateOptionsMenu(Menu menu) { 28 | MenuInflater inflater = getMenuInflater(); 29 | inflater.inflate(R.menu.form_actionbar, menu); 30 | return true; 31 | } 32 | 33 | @Override 34 | public boolean onOptionsItemSelected(MenuItem item) { 35 | boolean result = super.onOptionsItemSelected(item); 36 | if (validateForm()) { 37 | saveTask(); 38 | result = true; 39 | } 40 | return result; 41 | } 42 | 43 | private boolean validateForm() { 44 | return !TextUtils.isEmpty(titleText.getText()); 45 | } 46 | 47 | private void saveTask() { 48 | String title = titleText.getText().toString(); 49 | String notes = notesText.getText().toString(); 50 | 51 | ObjectContext ctx = TodoApp.getMainObjectContext(); 52 | 53 | Task newTask = ctx.newObject(Task.class); 54 | newTask.setTitle(title); 55 | newTask.setNotes(notes); 56 | 57 | ctx.save(); 58 | 59 | finish(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/Task.java: -------------------------------------------------------------------------------- 1 | package org.example.todo; 2 | 3 | public class Task extends _Task { 4 | 5 | public Task() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/TodoApp.java: -------------------------------------------------------------------------------- 1 | package org.example.todo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.github.dkharrat.nexusdata.core.*; 6 | import com.github.dkharrat.nexusdata.metamodel.ObjectModel; 7 | import com.github.dkharrat.nexusdata.store.AndroidSqlPersistentStore; 8 | import java.io.IOException; 9 | 10 | public class TodoApp extends Application { 11 | 12 | private static PersistentStoreCoordinator storeCoordinator; 13 | private static ObjectContext mainObjectContext; 14 | private static TodoApp app; 15 | 16 | @Override 17 | public void onCreate() { 18 | app = this; 19 | super.onCreate(); 20 | } 21 | 22 | public static PersistentStoreCoordinator getStoreCoordinator() { 23 | if (storeCoordinator == null) { 24 | ObjectModel model; 25 | try { 26 | model = new ObjectModel(app.getAssets().open("todo.model.json")); 27 | } catch (IOException ex) { 28 | throw new RuntimeException("Could not find models file", ex); 29 | } 30 | 31 | storeCoordinator = new PersistentStoreCoordinator(model); 32 | 33 | Context ctx = app.getApplicationContext(); 34 | PersistentStore cacheStore = new AndroidSqlPersistentStore(ctx, ctx.getDatabasePath("todo")); 35 | storeCoordinator.addStore(cacheStore); 36 | } 37 | 38 | return storeCoordinator; 39 | } 40 | 41 | public static ObjectContext getMainObjectContext() { 42 | if (mainObjectContext == null) { 43 | mainObjectContext = new ObjectContext(getStoreCoordinator()); 44 | 45 | ObjectContextNotifier.registerListener(new ObjectContextNotifier.DefaultObjectContextListener() { 46 | @Override 47 | public void onPostSave(ObjectContext context, ChangedObjectsSet changedObjects) { 48 | if (context != mainObjectContext && context.getPersistentStoreCoordinator() == mainObjectContext.getPersistentStoreCoordinator()) { 49 | mainObjectContext.mergeChangesFromSaveNotification(changedObjects); 50 | } 51 | } 52 | }); 53 | } 54 | 55 | return mainObjectContext; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/User.java: -------------------------------------------------------------------------------- 1 | package org.example.todo; 2 | 3 | public class User extends _User { 4 | 5 | public User() { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/_Task.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package org.example.todo; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _Task extends ManagedObject { 10 | 11 | public interface Property { 12 | final static String TITLE = "title"; 13 | final static String NOTES = "notes"; 14 | final static String DUE_BY = "dueBy"; 15 | final static String COMPLETED = "completed"; 16 | final static String PRIORITY = "priority"; 17 | final static String ASSIGNED_TO = "assignedTo"; 18 | } 19 | 20 | public enum Priority { 21 | HIGH, 22 | MEDIUM, 23 | LOW, 24 | } 25 | 26 | public String getTitle() { 27 | return (String)getValue(Property.TITLE); 28 | } 29 | 30 | public void setTitle(String title) { 31 | setValue(Property.TITLE, title); 32 | } 33 | 34 | public String getNotes() { 35 | return (String)getValue(Property.NOTES); 36 | } 37 | 38 | public void setNotes(String notes) { 39 | setValue(Property.NOTES, notes); 40 | } 41 | 42 | public Date getDueBy() { 43 | return (Date)getValue(Property.DUE_BY); 44 | } 45 | 46 | public void setDueBy(Date dueBy) { 47 | setValue(Property.DUE_BY, dueBy); 48 | } 49 | 50 | public boolean isCompleted() { 51 | return (Boolean)getValue(Property.COMPLETED); 52 | } 53 | 54 | public void setCompleted(boolean completed) { 55 | setValue(Property.COMPLETED, completed); 56 | } 57 | 58 | public Priority getPriority() { 59 | return (Priority)getValue(Property.PRIORITY); 60 | } 61 | 62 | public void setPriority(Priority priority) { 63 | setValue(Property.PRIORITY, priority); 64 | } 65 | 66 | 67 | public User getAssignedTo() { 68 | return (User)getValue(Property.ASSIGNED_TO); 69 | } 70 | 71 | public void setAssignedTo(User assignedTo) { 72 | setValue(Property.ASSIGNED_TO, assignedTo); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /samples/todo/src/main/java/org/example/todo/_User.java: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY. 2 | 3 | package org.example.todo; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | import com.github.dkharrat.nexusdata.core.ManagedObject; 8 | 9 | abstract class _User extends ManagedObject { 10 | 11 | public interface Property { 12 | final static String NAME = "name"; 13 | final static String TASKS = "tasks"; 14 | } 15 | 16 | 17 | public String getName() { 18 | return (String)getValue(Property.NAME); 19 | } 20 | 21 | public void setName(String name) { 22 | setValue(Property.NAME, name); 23 | } 24 | 25 | 26 | @SuppressWarnings("unchecked") 27 | public Set getTasks() { 28 | return (Set)getValue(Property.TASKS); 29 | } 30 | 31 | public void setTasks(Set tasks) { 32 | setValue(Property.TASKS, tasks); 33 | } 34 | 35 | public void addTask(Task task) { 36 | getTasks().add(task); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/todo/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/samples/todo/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/todo/src/main/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/samples/todo/src/main/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/todo/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/samples/todo/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/todo/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkharrat/NexusData/e79127d33f8bbf76ea1b9dfaf730b781f4c6a082/samples/todo/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/todo/src/main/res/layout/list_item_with_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 20 | 26 | 27 | 30 | 31 | 37 | 38 | 39 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /samples/todo/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/todo/src/main/res/layout/new_task.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 16 | 17 | 18 | 21 | 22 | 26 | -------------------------------------------------------------------------------- /samples/todo/src/main/res/menu/form_actionbar.xml: -------------------------------------------------------------------------------- 1 |

2 | 6 | -------------------------------------------------------------------------------- /samples/todo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Todo 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'nexusdata' 2 | include 'modelgen' 3 | include 'samples:todo' 4 | --------------------------------------------------------------------------------