]> gerrit.simantics Code Review - simantics/sysdyn.git/commitdiff
git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@13135 ac1ea38d-2e2b...
authorniemisto <niemisto@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Wed, 18 Nov 2009 13:41:39 +0000 (13:41 +0000)
committerniemisto <niemisto@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Wed, 18 Nov 2009 13:41:39 +0000 (13:41 +0000)
55 files changed:
org.simantics.objmap/.classpath [new file with mode: 0644]
org.simantics.objmap/.hgignore [new file with mode: 0644]
org.simantics.objmap/.project [new file with mode: 0644]
org.simantics.objmap/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.simantics.objmap/META-INF/MANIFEST.MF [new file with mode: 0644]
org.simantics.objmap/build.properties [new file with mode: 0644]
org.simantics.objmap/doc/bidirectionalModel.graphml [new file with mode: 0644]
org.simantics.objmap/doc/bidirectionalModel.png [new file with mode: 0644]
org.simantics.objmap/doc/main.mediawiki [new file with mode: 0644]
org.simantics.objmap/doc/manual.mediawiki [new file with mode: 0644]
org.simantics.objmap/doc/objectMappingTerminology.graphml [new file with mode: 0644]
org.simantics.objmap/doc/objectMappingTerminology.png [new file with mode: 0644]
org.simantics.objmap/doc/triangleModel.graphml [new file with mode: 0644]
org.simantics.objmap/doc/triangleModel.png [new file with mode: 0644]
org.simantics.objmap/examples/org/simantics/objmap/examples/SysdynExample.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/IFunction.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/ILinkType.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/IMapping.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/IMappingListener.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/IMappingRule.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/IMappingSchema.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/MappingException.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/Mappings.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/Composition.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/GraphType.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedElement.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedElements.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedValue.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/factories/DataTypeUtils.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedElementRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedElementsRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedValueRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasClassRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasFieldRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasMethodRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/impl/Link.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/impl/Mapping.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/impl/RangeUpdateRequest.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/impl/UnidirectionalMapping.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/MappedElementRule.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/MappedElementsRule.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/ValueRule.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/domain/IDomainAccessor.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/domain/MappingUtils.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedObjectAccessor.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedObjectsAccessor.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedValueAccessor.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/factory/IClassRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/factory/IFieldRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/factory/IMethodRuleFactory.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/range/FieldAccessor.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/rules/range/IRangeAccessor.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/schema/MappingSchemas.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/schema/SimpleLinkType.java [new file with mode: 0644]
org.simantics.objmap/src/org/simantics/objmap/schema/SimpleSchema.java [new file with mode: 0644]

diff --git a/org.simantics.objmap/.classpath b/org.simantics.objmap/.classpath
new file mode 100644 (file)
index 0000000..23e107f
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>\r
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
+       <classpathentry kind="src" path="src"/>\r
+       <classpathentry kind="src" path="examples"/>\r
+       <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
diff --git a/org.simantics.objmap/.hgignore b/org.simantics.objmap/.hgignore
new file mode 100644 (file)
index 0000000..73df90f
--- /dev/null
@@ -0,0 +1,5 @@
+syntax: regexp\r
+^bin/\r
+\r
+syntax: glob\r
+*.svn/*
\ No newline at end of file
diff --git a/org.simantics.objmap/.project b/org.simantics.objmap/.project
new file mode 100644 (file)
index 0000000..82c191c
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>org.simantics.objmap</name>\r
+       <comment></comment>\r
+       <projects>\r
+       </projects>\r
+       <buildSpec>\r
+               <buildCommand>\r
+                       <name>org.eclipse.jdt.core.javabuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.pde.ManifestBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.pde.SchemaBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+       </buildSpec>\r
+       <natures>\r
+               <nature>org.eclipse.pde.PluginNature</nature>\r
+               <nature>org.eclipse.jdt.core.javanature</nature>\r
+       </natures>\r
+</projectDescription>\r
diff --git a/org.simantics.objmap/.settings/org.eclipse.jdt.core.prefs b/org.simantics.objmap/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..fc058b8
--- /dev/null
@@ -0,0 +1,8 @@
+#Wed Nov 11 10:38:27 EET 2009\r
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6\r
+org.eclipse.jdt.core.compiler.compliance=1.6\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.6\r
diff --git a/org.simantics.objmap/META-INF/MANIFEST.MF b/org.simantics.objmap/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..3085d0a
--- /dev/null
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Objmap
+Bundle-SymbolicName: org.simantics.objmap
+Bundle-Version: 0.1.0
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Require-Bundle: org.simantics.db;bundle-version="0.6.2",
+ gnu.trove2;bundle-version="2.0.4",
+ org.simantics.layer0.utils;bundle-version="0.6.2",
+ org.apache.log4j;bundle-version="1.2.15"
+Export-Package: org.simantics.objmap,
+ org.simantics.objmap.annotations,
+ org.simantics.objmap.annotations.meta,
+ org.simantics.objmap.rules,
+ org.simantics.objmap.rules.domain,
+ org.simantics.objmap.rules.factory,
+ org.simantics.objmap.rules.range,
+ org.simantics.objmap.schema
diff --git a/org.simantics.objmap/build.properties b/org.simantics.objmap/build.properties
new file mode 100644 (file)
index 0000000..9892149
--- /dev/null
@@ -0,0 +1,7 @@
+source.. = src/\r
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+               .\r
+src.includes = doc/,\\r
+               META-INF/,\\r
+               examples/\r
diff --git a/org.simantics.objmap/doc/bidirectionalModel.graphml b/org.simantics.objmap/doc/bidirectionalModel.graphml
new file mode 100644 (file)
index 0000000..2a52aa1
--- /dev/null
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">\r
+  <!--Created by yFiles for Java 2.7-->\r
+  <key for="graphml" id="d0" yfiles.type="resources"/>\r
+  <key attr.name="url" attr.type="string" for="node" id="d1"/>\r
+  <key attr.name="description" attr.type="string" for="node" id="d2">\r
+    <default/>\r
+  </key>\r
+  <key for="node" id="d3" yfiles.type="nodegraphics"/>\r
+  <key attr.name="url" attr.type="string" for="edge" id="d4"/>\r
+  <key attr.name="description" attr.type="string" for="edge" id="d5">\r
+    <default/>\r
+  </key>\r
+  <key for="edge" id="d6" yfiles.type="edgegraphics"/>\r
+  <graph edgedefault="directed" id="G">\r
+    <node id="n0">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="127.0" x="186.0" y="200.0"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="106.720703125" x="10.1396484375" y="5.6494140625">Intermediate model</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <node id="n1">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="103.0" x="-21.0" y="200.0"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="55.369140625" x="23.8154296875" y="5.6494140625">Database</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <node id="n2">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="103.0" x="417.0" y="200.0"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="35.34765625" x="33.826171875" y="5.6494140625">Editor</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <edge id="e0" source="n1" target="n0">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">\r
+            <y:Point x="134.75500000000028" y="185.0425000000001"/>\r
+          </y:Path>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="head" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.017578125" x="13.93121093750014" y="-34.47918379864251">reads</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+    <edge id="e1" source="n0" target="n2">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">\r
+            <y:Point x="363.0000000000002" y="190.6400000000001"/>\r
+          </y:Path>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="shead" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="56.01953125" x="1.5527343750001137" y="-33.057650409984035">visualizes</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+    <edge id="e2" source="n2" target="n0">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">\r
+            <y:Point x="365.0000000000002" y="237.3600000000001"/>\r
+          </y:Path>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="stail" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="48.68359375" x="-54.904296874999886" y="13.861448547068562">modifies</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+    <edge id="e3" source="n0" target="n1">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">\r
+            <y:Point x="133.78500000000014" y="234.19250000000017"/>\r
+          </y:Path>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="tail" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="35.3359375" x="-48.33796874999993" y="10.017335507498103">writes</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+  </graph>\r
+  <data key="d0">\r
+    <y:Resources/>\r
+  </data>\r
+</graphml>\r
diff --git a/org.simantics.objmap/doc/bidirectionalModel.png b/org.simantics.objmap/doc/bidirectionalModel.png
new file mode 100644 (file)
index 0000000..77bcc30
Binary files /dev/null and b/org.simantics.objmap/doc/bidirectionalModel.png differ
diff --git a/org.simantics.objmap/doc/main.mediawiki b/org.simantics.objmap/doc/main.mediawiki
new file mode 100644 (file)
index 0000000..97c2fb3
--- /dev/null
@@ -0,0 +1,26 @@
+'''org.simantics.objmap'''  is a framework for bidirectional synchronization between Simantics database and Java objects. \r
+\r
+See [[org.simantics.template_Manual|Manual]] for details.\r
+\r
+= Dependencies=\r
+\r
+* [[org.simantics.db]]\r
+* gnu.trove2\r
+\r
+There is a plan to split the plugin into two plugins such that the other will be database-independent and contain all annotations.\r
+\r
+=Download=\r
+\r
+{| style="background-color: #e9e9e9; border: 1px solid #aaaaaa; width: 75%;"\r
+| '''Version'''\r
+| '''Date'''\r
+| '''Download'''\r
+|- style="background-color: #f9f9f9; " |\r
+| 0.1.0\r
+| 12.11.2009\r
+| [[Media:org.simantics.objmap-0.1.0.zip|org.simantics.objmap-0.1.0.zip]] \r
+|}\r
+\r
+=Current Development=\r
+The current version is released mostly for comments and is not yet ready for deployment. The development plan is at the end of the [[org.simantics.objmap_Manual#TODO|manual]]. \r
+\r
diff --git a/org.simantics.objmap/doc/manual.mediawiki b/org.simantics.objmap/doc/manual.mediawiki
new file mode 100644 (file)
index 0000000..c3bab96
--- /dev/null
@@ -0,0 +1,167 @@
+= Introduction =\r
+\r
+When implementing an editor in Simantics platform, it is very common that the graph representation cannot be directly used, but the editor needs to create an intermediate model of the subgraph it edits. Some reasons for this:\r
+* Accessing the database directly is not fast enough.\r
+* An editor using the database directly holds frequently long read locks and cannot operate during write transactions.\r
+* The editor needs to store auxiliary objects attached to the model.\r
+* Editor modifies the intermediate model speculatively before the modification is committed to the database or canceled.\r
+* The modifications in database cannot be applied in the editor immediately (for example during rendering).\r
+* Third-party component requires certain classes to be used.\r
+* Editor needs to be backend agnostic and cannot directly use database api.\r
+\r
+There are two different approaches for implementing the intermediate model:\r
+; Triangle model\r
+: The editor modifies the database directly and the changes in the database are eventually propagated to the intermediate model. The editor doesn't change the intermediate model directly.\r
+[[Image:triangleModel.png]] \r
+; Bidirectional model\r
+: The editor operates only using the intermediate model and modifications are updated from intermediate model to the database and vice versa. \r
+[[Image:bidirectionalModel.png]] \r
+\r
+By experience, the triangle model is easier to implement correctly in particular when resources are linked to each other in complex fashion. The  <code>org.simantics.objmap</code>-plugin tries to make the implementation of bidirectional model easier by providing a framework for defining declaratively the update procedure between database and intermediate model. It can also be used with triangle model only for one direction or with hybrid model where some operations are implemented using the intermediate model and other modifying the database directly.\r
+\r
+= Design principles =\r
+\r
+; Symmetric\r
+: For every operation from database to Java objects there is a corresponding operation from Java objects to database. This makes the framework easier to learn and undestand. \r
+; Non-intrusive\r
+: The Java objects used with the framework do not need to implement any specific interface or follow some specific naming convention. The mapping schema can be defined using annotations or separately from the class definition.\r
+; Support for different use scenarios\r
+:* bidirectional / unidirectional\r
+:* one shot / continuous\r
+:* automatic listening / manual updating\r
+; One-to-one\r
+: For every resource there is a single corresponding Java object and vise versa. This makes the framework easier to understand. It is not a transformation framework.  \r
+\r
+= Concepts =\r
+\r
+''Mapping'' consists of a set of resources called a ''domain'', a set of Java objects called a ''range'' and a collection of ''links''. Each link is attached to exactly one domain and range element and each domain and range element has at most one link attached to it. Additionally the link has a ''link type''  that contains requirements for the domain and range elements in the same link.\r
+\r
+[[Image:objectMappingTerminology.png]]\r
+\r
+A mapping is ''up-to-date'' if every domain and range element has a link and all links satisfy the requirements of their link types. The links of up-to-date mapping form a bijection from domain to range.\r
+\r
+A ''mapping schema'' associates all domain and range elements with a link type. It is used to add new domain and range elements to the mapping.\r
+\r
+= Mapping interface =\r
+\r
+The plugin represents a mapping with interface <code>org.simantics.objmap.IMapping</code>. The interface is symmetric in the sense that every operation on the domain of the mapping has also a counterpart that operates on the range. Typically, if one of the operations requires a read graph, its counterpart requires a write graph. We will describe only the methods operating on the domain of the mapping:\r
+\r
+ Set<Resource> getDomain();\r
\r
+Returns the domain of the mapping. All set operations are supported. Adding a new domain element does not automatically create a link to it. Removal of a domain element removes also a link and the target element, but does not remove the element from the database.  \r
+    \r
+ Collection<Resource> updateDomain(WriteGraph g) throws MappingException;\r
\r
+Updates all domain elements whose counterpart is modified and creates new domain elements for previously added range elements. Returns the collection of domain elements that were modified or created in the update process.\r
+\r
+ Object get(Resource domainElement);\r
+    \r
+Returns the counterpart of a domain element or null if the element does not belong to the domain or does not have a link.    \r
+    \r
+ Object map(ReadGraph g, Resource domainElement) throws MappingException;\r
+\r
+A convenience method that adds a domain element to the mapping and immediately updates the mapping and returns the corresponding range element.  \r
+\r
+ void domainModified(Resource domainElement);\r
\r
+Tells the mapping that the domain element has been modified.  \r
+\r
+ boolean isDomainModified();\r
\r
+Tells if some domain elements have been modified or added. \r
\r
+ Collection<Resource> getConflictingDomainElements();\r
+\r
+Returns a collection of domain elements which have been modified and also their counterparts in the mapping are modified. These elements are in conflict in the sense that the updating domain and range in different orders may produce different results.\r
+\r
+ void addMappingListener(IMappingListener listener);\r
+ void removeMappingListener(IMappingListener listener);\r
+\r
+Adds or removes a listener for domain and range modifications.\r
+\r
+= Defining a mapping schema =\r
+\r
+The primary way for defining a mapping schema is to use Java annotations. The current annotation support is still lacking. Only the following annotations are supported:\r
+; GraphType(uri)\r
+: Specifies the domain type that the class corresponds to.\r
+; RelatedValue(uri)\r
+: Specifies a correspondence between a field and functional property.\r
+; RelatedElement(uri)\r
+: Specifies a correspondence between a field and functional relation\r
+; RelatedElements(uri)\r
+: Specifies a correspondence between a field and a relation. The type of the field has to be a collection.\r
+\r
+== Example ==\r
+    \r
+Suppose we have the following annotated classes:\r
+ @GraphType("http://www.simantics.org/Sysdyn#Configuration")\r
+ static class Configuration {\r
+     @RelatedElements("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#ConsistsOf")\r
+     Collection<Component> components; \r
+ }\r
\r
+ static abstract class Component {        \r
+ }\r
\r
+ @GraphType("http://www.simantics.org/Sysdyn#Dependency")\r
+ static class Dependency extends Component {\r
+     @RelatedElement("http://www.simantics.org/Sysdyn#HasTail")\r
+     Variable tail;\r
+     @RelatedElement("http://www.simantics.org/Sysdyn#HasHead")\r
+     Auxiliary head;\r
+ }\r
\r
+ static abstract class Variable extends Component {\r
+     @RelatedValue("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#HasName")\r
+     String name;\r
+ }\r
\r
+ @GraphType("http://www.simantics.org/Sysdyn#Auxiliary")\r
+ static class Auxiliary extends Variable {\r
+ }\r
+\r
+Them the schema can be defined as follows:\r
+ SimpleSchema schema = new SimpleSchema();\r
+ schema.addLinkType(MappingSchemas.fromAnnotations(g, Configuration.class));\r
+ schema.addLinkType(MappingSchemas.fromAnnotations(g, Dependency.class));\r
+ schema.addLinkType(MappingSchemas.fromAnnotations(g, Auxiliary.class));\r
+\r
+= Using the mapping interface =\r
+\r
+Assume that a mapping scheme <code>scheme</code> has already been defined and <code>modelRoot</code> is the root resource of the model that the editor edits. Then the model is created as follows:\r
+ IMapping mapping = Mappings.create(scheme);\r
+ in read transaction {\r
+     MyModel model = (MyModel)mapping.map(graph, modelRoot);\r
+ } \r
\r
+There are different ways how the mapping can be updated. The following code forces update for all domain elements. \r
+ in read transaction {\r
+     for(Resource r : mapping.getDomain())\r
+         mapping.domainModified(r);\r
+     mapping.updateRange(graph);\r
+ }\r
\r
+If the range elements have some kind of "dirty" flags, the update can be optimized:\r
+ in write transaction {\r
+     for(Object obj : mapping.getRange())\r
+         if(obj implements MyObject && ((MyObject)obj).isDirty())\r
+             mapping.rangeModified(obj);\r
+     mapping.updateDomain(graph);\r
+ }\r
\r
+Often the editor has to update some auxiliary structures when the mapping modifies the range. This can be implemented for example as:\r
+ for(Object obj : mapping.updateRange(graph))\r
+     if(obj implements MyObject)\r
+         ((MyObject)obj).updateAuxiliary();\r
+\r
+The most convenient way for updating the target would be to add graph request listeners for each domain element in the mapping. This is not yet implemented although the current interface should support this without modifications. Currently the only way to listen the database changes is to listen the request that is used to call the updateRange-method.\r
+\r
+= Development plan =\r
+\r
+By priority:\r
+* Automatic listening of database changes: marks domain elements modified.\r
+* More complete annotations\r
+* Utilizing declarations in ontologies: for example full URIs of relations are not needed because relations are declared in types. Also the validity of annotation can be checked.\r
+* A separate plugin containing only mapping annotations so that the plugin can be used without introducing a dependency to org.simantics.db.\r
+* Composition annotation that can be used to remove elements whose parents are removed or update elements whose parents are marked modified.  \r
+* Support for copy-paste and other extra mapping issues. \r
diff --git a/org.simantics.objmap/doc/objectMappingTerminology.graphml b/org.simantics.objmap/doc/objectMappingTerminology.graphml
new file mode 100644 (file)
index 0000000..c3cc6b2
--- /dev/null
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">\r
+  <!--Created by yFiles for Java 2.7-->\r
+  <key for="graphml" id="d0" yfiles.type="resources"/>\r
+  <key attr.name="url" attr.type="string" for="node" id="d1"/>\r
+  <key attr.name="description" attr.type="string" for="node" id="d2">\r
+    <default/>\r
+  </key>\r
+  <key for="node" id="d3" yfiles.type="nodegraphics"/>\r
+  <key attr.name="url" attr.type="string" for="edge" id="d4"/>\r
+  <key attr.name="description" attr.type="string" for="edge" id="d5">\r
+    <default/>\r
+  </key>\r
+  <key for="edge" id="d6" yfiles.type="edgegraphics"/>\r
+  <graph edgedefault="directed" id="G">\r
+    <node id="n0" yfiles.foldertype="group">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ProxyAutoBoundsNode>\r
+          <y:Realizers active="0">\r
+            <y:GroupNode>\r
+              <y:Geometry height="92.0" width="532.0" x="99.0" y="298.0"/>\r
+              <y:Fill color="#F5F5F5" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="22.37646484375" modelName="free" modelPosition="anywhere" textColor="#000000" visible="true" width="532.0" x="0.0" y="0.0">mapping</y:NodeLabel>\r
+              <y:Shape type="rectangle"/>\r
+              <y:State closed="false" innerGraphDisplayEnabled="false"/>\r
+              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>\r
+              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="20" topF="20.0"/>\r
+            </y:GroupNode>\r
+            <y:GroupNode>\r
+              <y:Geometry height="80.0" width="100.0" x="-50.0" y="-30.0"/>\r
+              <y:Fill color="#CAECFF84" transparent="false"/>\r
+              <y:BorderStyle color="#666699" type="dotted" width="1.0"/>\r
+              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#99CCFF" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="4.0" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="100.0" x="0.0" y="0.0"/>\r
+              <y:Shape type="roundrectangle"/>\r
+              <y:State closed="true" innerGraphDisplayEnabled="false"/>\r
+              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>\r
+              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>\r
+            </y:GroupNode>\r
+          </y:Realizers>\r
+        </y:ProxyAutoBoundsNode>\r
+      </data>\r
+      <graph edgedefault="directed" id="n0:">\r
+        <node id="n0::n0">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="513.0" y="333.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n1">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="507.0" y="339.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n2">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="319.5" y="333.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n3">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="313.5" y="339.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n4">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="126.0" y="333.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n5">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="120.0" y="339.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n6">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="114.0" y="345.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="89.3828125" x="6.80859375" y="5.6494140625">domain element</y:NodeLabel>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n7">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="307.5" y="345.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="22.005859375" x="40.4970703125" y="5.6494140625">link</y:NodeLabel>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <node id="n0::n8">\r
+          <data key="d3">\r
+            <y:ShapeNode>\r
+              <y:Geometry height="30.0" width="103.0" x="501.0" y="345.0"/>\r
+              <y:Fill color="#FFFFFF" transparent="false"/>\r
+              <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="80.716796875" x="11.1416015625" y="5.6494140625">range element</y:NodeLabel>\r
+              <y:Shape type="rectangle"/>\r
+            </y:ShapeNode>\r
+          </data>\r
+        </node>\r
+        <edge id="n0::e0" source="n0::n7" target="n0::n8">\r
+          <data key="d6">\r
+            <y:PolyLineEdge>\r
+              <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>\r
+              <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+              <y:Arrows source="none" target="standard"/>\r
+              <y:BendStyle smoothed="false"/>\r
+            </y:PolyLineEdge>\r
+          </data>\r
+        </edge>\r
+        <edge id="n0::e1" source="n0::n7" target="n0::n6">\r
+          <data key="d6">\r
+            <y:PolyLineEdge>\r
+              <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>\r
+              <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+              <y:Arrows source="none" target="standard"/>\r
+              <y:BendStyle smoothed="false"/>\r
+            </y:PolyLineEdge>\r
+          </data>\r
+        </edge>\r
+      </graph>\r
+    </node>\r
+    <node id="n1">\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="103.0" x="313.5" y="426.58361904761904"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="49.5" y="13.0"/>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <node id="n2">\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="103.0" x="307.5" y="433.9028571428571"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="48.021484375" x="27.4892578125" y="5.6494140625">link type</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <edge id="e0" source="n0::n7" target="n2">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+  </graph>\r
+  <data key="d0">\r
+    <y:Resources/>\r
+  </data>\r
+</graphml>\r
diff --git a/org.simantics.objmap/doc/objectMappingTerminology.png b/org.simantics.objmap/doc/objectMappingTerminology.png
new file mode 100644 (file)
index 0000000..e9ee1fb
Binary files /dev/null and b/org.simantics.objmap/doc/objectMappingTerminology.png differ
diff --git a/org.simantics.objmap/doc/triangleModel.graphml b/org.simantics.objmap/doc/triangleModel.graphml
new file mode 100644 (file)
index 0000000..9ab3b29
--- /dev/null
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">\r
+  <!--Created by yFiles for Java 2.7-->\r
+  <key for="graphml" id="d0" yfiles.type="resources"/>\r
+  <key attr.name="url" attr.type="string" for="node" id="d1"/>\r
+  <key attr.name="description" attr.type="string" for="node" id="d2">\r
+    <default/>\r
+  </key>\r
+  <key for="node" id="d3" yfiles.type="nodegraphics"/>\r
+  <key attr.name="url" attr.type="string" for="edge" id="d4"/>\r
+  <key attr.name="description" attr.type="string" for="edge" id="d5">\r
+    <default/>\r
+  </key>\r
+  <key for="edge" id="d6" yfiles.type="edgegraphics"/>\r
+  <graph edgedefault="directed" id="G">\r
+    <node id="n0">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="127.0" x="186.0" y="200.0"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="106.720703125" x="10.1396484375" y="5.6494140625">Intermediate model</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <node id="n1">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="103.0" x="80.0" y="315.0"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="55.369140625" x="23.8154296875" y="5.6494140625">Database</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <node id="n2">\r
+      <data key="d2"/>\r
+      <data key="d3">\r
+        <y:ShapeNode>\r
+          <y:Geometry height="30.0" width="103.0" x="320.0" y="315.0"/>\r
+          <y:Fill color="#FFFFFF" transparent="false"/>\r
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>\r
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="35.34765625" x="33.826171875" y="5.6494140625">Editor</y:NodeLabel>\r
+          <y:Shape type="rectangle"/>\r
+        </y:ShapeNode>\r
+      </data>\r
+    </node>\r
+    <edge id="e0" source="n1" target="n0">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="tail" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.017578125" x="55.20320262079653" y="-51.8505859375">reads</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+    <edge id="e1" source="n0" target="n2">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="tail" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="56.01953125" x="57.00669661812162" y="33.1494140625">visualizes</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+    <edge id="e2" source="n2" target="n1">\r
+      <data key="d6">\r
+        <y:PolyLineEdge>\r
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>\r
+          <y:LineStyle color="#000000" type="line" width="1.0"/>\r
+          <y:Arrows source="none" target="standard"/>\r
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" modelName="six_pos" modelPosition="tail" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="48.68359375" x="-92.841796875" y="2.0">modifies</y:EdgeLabel>\r
+          <y:BendStyle smoothed="false"/>\r
+        </y:PolyLineEdge>\r
+      </data>\r
+    </edge>\r
+  </graph>\r
+  <data key="d0">\r
+    <y:Resources/>\r
+  </data>\r
+</graphml>\r
diff --git a/org.simantics.objmap/doc/triangleModel.png b/org.simantics.objmap/doc/triangleModel.png
new file mode 100644 (file)
index 0000000..0918ef8
Binary files /dev/null and b/org.simantics.objmap/doc/triangleModel.png differ
diff --git a/org.simantics.objmap/examples/org/simantics/objmap/examples/SysdynExample.java b/org.simantics.objmap/examples/org/simantics/objmap/examples/SysdynExample.java
new file mode 100644 (file)
index 0000000..57092cb
--- /dev/null
@@ -0,0 +1,51 @@
+package org.simantics.objmap.examples;\r
+\r
+import java.util.Collection;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.IMappingSchema;\r
+import org.simantics.objmap.annotations.GraphType;\r
+import org.simantics.objmap.annotations.RelatedElement;\r
+import org.simantics.objmap.annotations.RelatedElements;\r
+import org.simantics.objmap.annotations.RelatedValue;\r
+import org.simantics.objmap.schema.MappingSchemas;\r
+import org.simantics.objmap.schema.SimpleSchema;\r
+\r
+public class SysdynExample {\r
+\r
+    @GraphType("http://www.simantics.org/Sysdyn#Configuration")\r
+    static class Configuration {\r
+        @RelatedElements("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#ConsistsOf")\r
+        Collection<Component> components; \r
+    }\r
+    \r
+    static abstract class Component {        \r
+    }\r
+    \r
+    @GraphType("http://www.simantics.org/Sysdyn#Dependency")\r
+    static class Dependency extends Component {\r
+        @RelatedElement("http://www.simantics.org/Sysdyn#HasTail")\r
+        Variable tail;\r
+        @RelatedElement("http://www.simantics.org/Sysdyn#HasHead")\r
+        Auxiliary head;\r
+    }\r
+    \r
+    static abstract class Variable extends Component {\r
+        @RelatedValue("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#HasName")\r
+        String name;\r
+    }\r
+    \r
+    @GraphType("http://www.simantics.org/Sysdyn#Auxiliary")\r
+    static class Auxiliary extends Variable {\r
+    }\r
+    \r
+    public static IMappingSchema createSchema(ReadGraph g) throws DatabaseException, InstantiationException, IllegalAccessException {\r
+        SimpleSchema schema = new SimpleSchema();\r
+        schema.addLinkType(MappingSchemas.fromAnnotations(g, Configuration.class));\r
+        schema.addLinkType(MappingSchemas.fromAnnotations(g, Dependency.class));\r
+        schema.addLinkType(MappingSchemas.fromAnnotations(g, Auxiliary.class));\r
+        return schema;\r
+    }   \r
+    \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/IFunction.java b/org.simantics.objmap/src/org/simantics/objmap/IFunction.java
new file mode 100644 (file)
index 0000000..a2be290
--- /dev/null
@@ -0,0 +1,13 @@
+package org.simantics.objmap;\r
+\r
+/**\r
+ * A generic function object that throws MappingExceptions.\r
+ * \r
+ * @author Hannu Niemistö\r
+ *\r
+ * @param <D> Domain of the function\r
+ * @param <R> Range of the function\r
+ */\r
+public interface IFunction<D, R> {\r
+       R get(D element) throws MappingException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/ILinkType.java b/org.simantics.objmap/src/org/simantics/objmap/ILinkType.java
new file mode 100644 (file)
index 0000000..db121ad
--- /dev/null
@@ -0,0 +1,21 @@
+package org.simantics.objmap;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+\r
+/**\r
+ * Contains rules for how a link should be created and maintained.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface ILinkType extends IMappingRule {\r
+    /**\r
+     * Creates a domain element based on known range element.\r
+     */\r
+       Resource createDomainElement(WriteGraph g, Object rangeElement) throws MappingException;\r
+       \r
+       /**\r
+     * Creates a range element based on known domain element.\r
+     */\r
+       Object createRangeElement(ReadGraph g, Resource domainElement) throws MappingException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/IMapping.java b/org.simantics.objmap/src/org/simantics/objmap/IMapping.java
new file mode 100644 (file)
index 0000000..64678b8
--- /dev/null
@@ -0,0 +1,127 @@
+package org.simantics.objmap;\r
+\r
+import java.util.Collection;\r
+import java.util.Set;\r
+\r
+import org.simantics.db.Disposable;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+\r
+/**\r
+ * A mapping consists of domain (a set of resources), range (a set of Java objects) and\r
+ * a set of links relating them. The mapping is used to propagate modifications of\r
+ * domain elements to range and vice versa. \r
+ * \r
+ * @see <a href="http://www.simantics.org/wiki/index.php/org.simantics.objmap_Manual#Concepts">Manual</a>\r
+ * \r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IMapping extends Disposable {\r
+\r
+    /**\r
+     * Returns the domain of the mapping. All set operations are supported.\r
+     * Adding a new domain element does not automatically create a link to it.\r
+     * Removal of a domain element removes also a link and the target element,\r
+     * but does not remove the element from the database.\r
+     */\r
+    Set<Resource> getDomain();\r
+\r
+    /**\r
+     * Returns the range of the mapping. All set operations are supported.\r
+     * Adding a new range element does not automatically create a link to it.\r
+     * Removal of a range element removes also a link and the domain element,\r
+     * but does not remove the domain element from the database.\r
+     */\r
+    Set<Object> getRange();\r
+\r
+    /**\r
+     * Updates all domain elements whose counterpart is modified and creates new\r
+     * domain elements for previously added range elements. Returns the\r
+     * collection of domain elements that were modified or created in the update\r
+     * process.\r
+     */\r
+    Collection<Resource> updateDomain(WriteGraph g) throws MappingException;\r
+\r
+    /**\r
+     * Updates all range elements whose counterpart is modified and creates new\r
+     * range elements for previously added domain elements. Returns the\r
+     * collection of range elements that were modified or created in the update\r
+     * process.\r
+     */\r
+    Collection<Object> updateRange(ReadGraph g) throws MappingException;\r
+\r
+    /**\r
+     * Returns the counterpart of a domain element or null if the element does\r
+     * not belong to the domain or does not have a link.\r
+     */\r
+    Object get(Resource domainElement);\r
+\r
+    /**\r
+     * Returns the counterpart of a range element or null if the element does\r
+     * not belong to the range or does not have a link.\r
+     */\r
+    Resource inverseGet(Object rangeElement);\r
+\r
+    /**\r
+     * A convenience method that adds a domain element to the mapping and\r
+     * immediately updates the mapping and returns the corresponding range\r
+     * element.\r
+     */\r
+    Object map(ReadGraph g, Resource domainElement) throws MappingException;\r
+\r
+    /**\r
+     * A convenience method that adds a range element to the mapping and\r
+     * immediately updates the mapping and returns the corresponding domain\r
+     * element.\r
+     */\r
+    Resource inverseMap(WriteGraph g, Object rangeElement)\r
+            throws MappingException;\r
+\r
+    /**\r
+     * Tells the mapping that the domain element has been modified.\r
+     */\r
+    void domainModified(Resource domainElement);\r
+\r
+    /**\r
+     * Tells the mapping that the range element has been modified.\r
+     */\r
+    void rangeModified(Object rangeElement);\r
+\r
+    /**\r
+     * Tells if some domain elements have been modified or added.\r
+     */\r
+    boolean isDomainModified();\r
+\r
+    /**\r
+     * Tells if some range elements have been modified or added.\r
+     */\r
+    boolean isRangeModified();\r
+\r
+    /**\r
+     * Returns a collection of domain elements which have been modified and also\r
+     * their counterparts in the mapping are modified. These elements are in\r
+     * conflict in the sense that the updating domain and range in different\r
+     * orders may produce different results.\r
+     */\r
+    Collection<Resource> getConflictingDomainElements();\r
+\r
+    /**\r
+     * Returns a collection of range elements which have been modified and also\r
+     * their counterparts in the mapping are modified. These elements are in\r
+     * conflict in the sense that the updating domain and range in different\r
+     * orders may produce different results.\r
+     */\r
+    Collection<Object> getConflictingRangeElements();\r
+\r
+    /**\r
+     * Adds a listener for domain and range modifications.\r
+     */\r
+    void addMappingListener(IMappingListener listener);\r
+\r
+    /**\r
+     * Removes a previously added listener.\r
+     */\r
+    void removeMappingListener(IMappingListener listener);\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/IMappingListener.java b/org.simantics.objmap/src/org/simantics/objmap/IMappingListener.java
new file mode 100644 (file)
index 0000000..766348a
--- /dev/null
@@ -0,0 +1,19 @@
+package org.simantics.objmap;\r
+\r
+/**\r
+ * Listens modifications in a mapping.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IMappingListener {\r
+    /**\r
+     * Called when some domain element is modified or created\r
+     * and the mapping was previously up to date.\r
+     */\r
+       void domainModified();\r
+       \r
+       /**\r
+     * Called when some range element is modified or created\r
+     * and the mapping was previously up to date.\r
+     */\r
+       void rangeModified();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/IMappingRule.java b/org.simantics.objmap/src/org/simantics/objmap/IMappingRule.java
new file mode 100644 (file)
index 0000000..211e02f
--- /dev/null
@@ -0,0 +1,33 @@
+package org.simantics.objmap;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+\r
+/**\r
+ * A rule for how domain and range elements are related to each other. \r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IMappingRule {\r
+    /**\r
+     * Modifies the domain element so that it corresponds to the range element.\r
+     * @param g write transaction\r
+     * @param map unidirectional view of the current mapping\r
+     * @param domainElement the domain element that is updated\r
+     * @param rangeElement the range element that corresponds to the domain element\r
+     * @return true if the rule made some modifications\r
+     * @throws MappingException \r
+     */    \r
+       boolean updateDomain(WriteGraph g, IFunction<Object, Resource> map, Resource domainElement, Object rangeElement) throws MappingException;\r
+\r
+       /**\r
+        * Modifies the range element so that it corresponds to the domain element.\r
+        * @param g read transaction\r
+        * @param map unidirectional view of the current mapping\r
+        * @param domainElement the domain element that corresponds to the range element\r
+        * @param rangeElement the range element that is updated\r
+        * @return true if the rule made some modifications\r
+        * @throws MappingException\r
+        */\r
+       boolean updateRange(ReadGraph g, IFunction<Resource, Object> map, Resource domainElement, Object rangeElement) throws MappingException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/IMappingSchema.java b/org.simantics.objmap/src/org/simantics/objmap/IMappingSchema.java
new file mode 100644 (file)
index 0000000..1dfbe43
--- /dev/null
@@ -0,0 +1,20 @@
+package org.simantics.objmap;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+\r
+/**\r
+ * Specifies the link types of new elements added to a mapping.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IMappingSchema {\r
+    /**\r
+     * @return Link type that should be used for the element.\r
+     */\r
+       ILinkType linkTypeOfDomainElement(ReadGraph g, Resource element) throws MappingException;\r
+       \r
+       /**\r
+     * @return Link type that should be used for the element.\r
+     */\r
+       ILinkType linkTypeOfRangeElement(Object element) throws MappingException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/MappingException.java b/org.simantics.objmap/src/org/simantics/objmap/MappingException.java
new file mode 100644 (file)
index 0000000..6fa9f6a
--- /dev/null
@@ -0,0 +1,29 @@
+package org.simantics.objmap;\r
+\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+/**\r
+ * An exception thrown for any error during mapping methods.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class MappingException extends DatabaseException {\r
+\r
+       private static final long serialVersionUID = -4026122899414272427L;\r
+\r
+       public MappingException() {\r
+               super();\r
+       }\r
+\r
+       public MappingException(String message, Throwable cause) {\r
+               super(message, cause);\r
+       }\r
+\r
+       public MappingException(String message) {\r
+               super(message);\r
+       }\r
+\r
+       public MappingException(Throwable cause) {\r
+               super(cause);\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/Mappings.java b/org.simantics.objmap/src/org/simantics/objmap/Mappings.java
new file mode 100644 (file)
index 0000000..6c576fa
--- /dev/null
@@ -0,0 +1,38 @@
+package org.simantics.objmap;\r
+\r
+import org.simantics.objmap.impl.Mapping;\r
+import org.simantics.objmap.impl.UnidirectionalMapping;\r
+\r
+/**\r
+ * Static utility methods for mappings. \r
+ * @author Hannu Niemistö\r
+ */\r
+public class Mappings {\r
+       private Mappings() {}\r
+       \r
+       /**\r
+        * Creates a new mapping based on the given mapping schema. \r
+        * The created mapping is not thread-safe and will not\r
+        * listen database changes automatically.\r
+        */\r
+       public static IMapping createWithoutListening(IMappingSchema schema) {\r
+               return new Mapping(schema, false);\r
+       }\r
+       \r
+       /**\r
+     * Creates a new mapping based on the given mapping schema. \r
+     * The created mapping is not thread-safe. It listens database\r
+     * changes automatically.\r
+     */\r
+       public static IMapping createWithListening(IMappingSchema schema) {\r
+        return new Mapping(schema, true);\r
+    }\r
+       \r
+       /**\r
+        * Creates a mapping that supports only the direction from domain to range.\r
+        * Does not listen the database.\r
+        */\r
+       public static IMapping createUnidirectional(IMappingSchema schema) {\r
+           return new UnidirectionalMapping(schema);\r
+       }\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/Composition.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/Composition.java
new file mode 100644 (file)
index 0000000..eaf368c
--- /dev/null
@@ -0,0 +1,11 @@
+package org.simantics.objmap.annotations;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target({ElementType.FIELD,ElementType.METHOD})\r
+public @interface Composition {\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/GraphType.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/GraphType.java
new file mode 100644 (file)
index 0000000..d434b3c
--- /dev/null
@@ -0,0 +1,16 @@
+package org.simantics.objmap.annotations;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+/**\r
+ * Specifies the domain type that the class corresponds to. \r
+ * @author Hannu Niemistö\r
+ */\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.TYPE) \r
+public @interface GraphType {\r
+       String value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedElement.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedElement.java
new file mode 100644 (file)
index 0000000..d3d011b
--- /dev/null
@@ -0,0 +1,16 @@
+package org.simantics.objmap.annotations;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+import org.simantics.objmap.annotations.factories.RelatedElementRuleFactory;\r
+import org.simantics.objmap.annotations.meta.HasFieldRuleFactory;\r
+\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.FIELD)\r
+@HasFieldRuleFactory(RelatedElementRuleFactory.class)\r
+public @interface RelatedElement {\r
+       String value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedElements.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedElements.java
new file mode 100644 (file)
index 0000000..a619ab6
--- /dev/null
@@ -0,0 +1,16 @@
+package org.simantics.objmap.annotations;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+import org.simantics.objmap.annotations.factories.RelatedElementsRuleFactory;\r
+import org.simantics.objmap.annotations.meta.HasFieldRuleFactory;\r
+\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.FIELD)\r
+@HasFieldRuleFactory(RelatedElementsRuleFactory.class)\r
+public @interface RelatedElements {\r
+       String value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedValue.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/RelatedValue.java
new file mode 100644 (file)
index 0000000..e6138d9
--- /dev/null
@@ -0,0 +1,21 @@
+package org.simantics.objmap.annotations;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+import org.simantics.objmap.annotations.factories.RelatedValueRuleFactory;\r
+import org.simantics.objmap.annotations.meta.HasFieldRuleFactory;\r
+\r
+/**\r
+ * Specifies a correspondence between a field and \r
+ * functional property.\r
+ * @author Hannu Niemistö\r
+ */\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.FIELD)\r
+@HasFieldRuleFactory(RelatedValueRuleFactory.class)\r
+public @interface RelatedValue {\r
+       String value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/DataTypeUtils.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/DataTypeUtils.java
new file mode 100644 (file)
index 0000000..8d38fbd
--- /dev/null
@@ -0,0 +1,46 @@
+package org.simantics.objmap.annotations.factories;\r
+\r
+import org.simantics.db.Builtins;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+\r
+public class DataTypeUtils {\r
+    \r
+    public static Resource dataTypeOfClass(ReadGraph g, Class<?> clazz) {\r
+        Builtins b = g.getBuiltins();\r
+        if(clazz.equals(Double.class) || clazz.equals(double.class))\r
+            return b.Double;\r
+        else if(clazz.equals(String.class))\r
+            return b.String;\r
+        else if(clazz.equals(Integer.class) || clazz.equals(int.class))\r
+            return b.Integer;\r
+        else if(clazz.equals(Float.class) || clazz.equals(float.class))\r
+            return b.Float;\r
+        else if(clazz.equals(Boolean.class) || clazz.equals(boolean.class))\r
+            return b.Boolean;\r
+        else if(clazz.equals(Long.class) || clazz.equals(long.class))\r
+            return b.Long;\r
+        else if(clazz.equals(Byte.class) || clazz.equals(byte.class))\r
+            return b.Byte;\r
+        \r
+        else if(clazz.equals(double[].class))\r
+            return b.DoubleArray;\r
+        else if(clazz.equals(int[].class))\r
+            return b.IntegerArray;\r
+        else if(clazz.equals(byte[].class))\r
+            return b.ByteArray;\r
+        else if(clazz.equals(float[].class))\r
+            return b.FloatArray;\r
+        else if(clazz.equals(boolean[].class))\r
+            return b.BooleanArray;\r
+        else if(clazz.equals(String[].class))\r
+            return b.StringArray;\r
+        else if(clazz.equals(long[].class))\r
+            return b.LongArray;\r
+        else {\r
+               System.out.println("Couldn't find a data type for " + clazz);\r
+            return null;\r
+        }\r
+    }\r
+    \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedElementRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedElementRuleFactory.java
new file mode 100644 (file)
index 0000000..f4499fa
--- /dev/null
@@ -0,0 +1,28 @@
+package org.simantics.objmap.annotations.factories;\r
+\r
+import java.lang.annotation.Annotation;\r
+import java.lang.reflect.Field;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.ResourceNotFoundException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.annotations.RelatedElement;\r
+import org.simantics.objmap.rules.MappedElementRule;\r
+import org.simantics.objmap.rules.domain.RelatedObjectAccessor;\r
+import org.simantics.objmap.rules.factory.IFieldRuleFactory;\r
+import org.simantics.objmap.rules.range.FieldAccessor;\r
+\r
+public class RelatedElementRuleFactory implements IFieldRuleFactory {\r
+\r
+    @Override\r
+    public IMappingRule create(ReadGraph g, Annotation _annotation, Field field) throws ResourceNotFoundException, ValidationException, ServiceException {\r
+        RelatedElement annotation = (RelatedElement)_annotation;\r
+        return new MappedElementRule(\r
+                new RelatedObjectAccessor(g.getResourceByURI(annotation.value())),\r
+                new FieldAccessor<Object>(field)\r
+                );\r
+    }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedElementsRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedElementsRuleFactory.java
new file mode 100644 (file)
index 0000000..74011e4
--- /dev/null
@@ -0,0 +1,29 @@
+package org.simantics.objmap.annotations.factories;\r
+\r
+import java.lang.annotation.Annotation;\r
+import java.lang.reflect.Field;\r
+import java.util.Collection;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.ResourceNotFoundException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.annotations.RelatedElements;\r
+import org.simantics.objmap.rules.MappedElementsRule;\r
+import org.simantics.objmap.rules.domain.RelatedObjectsAccessor;\r
+import org.simantics.objmap.rules.factory.IFieldRuleFactory;\r
+import org.simantics.objmap.rules.range.FieldAccessor;\r
+\r
+public class RelatedElementsRuleFactory implements IFieldRuleFactory {\r
+\r
+    @Override\r
+    public IMappingRule create(ReadGraph g, Annotation _annotation, Field field) throws ResourceNotFoundException, ValidationException, ServiceException {\r
+        RelatedElements annotation = (RelatedElements)_annotation;\r
+        return new MappedElementsRule(\r
+                new RelatedObjectsAccessor(g.getResourceByURI(annotation.value())),\r
+                new FieldAccessor<Collection<Object>>(field)\r
+                );\r
+    }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedValueRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/factories/RelatedValueRuleFactory.java
new file mode 100644 (file)
index 0000000..7bbecaa
--- /dev/null
@@ -0,0 +1,33 @@
+package org.simantics.objmap.annotations.factories;\r
+\r
+import java.lang.annotation.Annotation;\r
+import java.lang.reflect.Field;\r
+\r
+import org.simantics.db.Builtins;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.ResourceNotFoundException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.annotations.RelatedValue;\r
+import org.simantics.objmap.rules.ValueRule;\r
+import org.simantics.objmap.rules.domain.RelatedValueAccessor;\r
+import org.simantics.objmap.rules.factory.IFieldRuleFactory;\r
+import org.simantics.objmap.rules.range.FieldAccessor;\r
+\r
+public class RelatedValueRuleFactory implements IFieldRuleFactory {\r
+\r
+    @Override\r
+    public IMappingRule create(ReadGraph g, Annotation _annotation, Field field) throws ResourceNotFoundException, ValidationException, ServiceException {\r
+        RelatedValue annotation = (RelatedValue)_annotation;\r
+        Builtins b = g.getBuiltins();\r
+        return new ValueRule(\r
+                new RelatedValueAccessor(\r
+                        g.getResourceByURI(annotation.value()), \r
+                        DataTypeUtils.dataTypeOfClass(g, field.getType())\r
+                    ),\r
+                new FieldAccessor<Object>(field)\r
+                );\r
+    }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasClassRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasClassRuleFactory.java
new file mode 100644 (file)
index 0000000..f556f88
--- /dev/null
@@ -0,0 +1,14 @@
+package org.simantics.objmap.annotations.meta;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+import org.simantics.objmap.rules.factory.IClassRuleFactory;\r
+\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.ANNOTATION_TYPE) \r
+public @interface HasClassRuleFactory {\r
+    Class<? extends IClassRuleFactory> value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasFieldRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasFieldRuleFactory.java
new file mode 100644 (file)
index 0000000..4f8c8ad
--- /dev/null
@@ -0,0 +1,14 @@
+package org.simantics.objmap.annotations.meta;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+import org.simantics.objmap.rules.factory.IFieldRuleFactory;\r
+\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.ANNOTATION_TYPE) \r
+public @interface HasFieldRuleFactory {\r
+    Class<? extends IFieldRuleFactory> value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasMethodRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/annotations/meta/HasMethodRuleFactory.java
new file mode 100644 (file)
index 0000000..273041b
--- /dev/null
@@ -0,0 +1,14 @@
+package org.simantics.objmap.annotations.meta;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+import org.simantics.objmap.rules.factory.IMethodRuleFactory;\r
+\r
+@Retention(RetentionPolicy.RUNTIME)\r
+@Target(ElementType.ANNOTATION_TYPE) \r
+public @interface HasMethodRuleFactory {\r
+    Class<? extends IMethodRuleFactory> value();\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/impl/Link.java b/org.simantics.objmap/src/org/simantics/objmap/impl/Link.java
new file mode 100644 (file)
index 0000000..45336d9
--- /dev/null
@@ -0,0 +1,29 @@
+/**\r
+ * \r
+ */\r
+package org.simantics.objmap.impl;\r
+\r
+import org.simantics.db.Resource;\r
+import org.simantics.objmap.ILinkType;\r
+\r
+/**\r
+ * An indication that the domain element corresponds to the range element\r
+ * in the mapping. The link type describes how source and target objects\r
+ * are updated. There are additionally flags for dirtiness of the link.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class Link {\r
+       public ILinkType type;\r
+       public Resource domainElement;\r
+       public Object rangeElement;\r
+       \r
+       public boolean domainModified = false;\r
+       public boolean rangeModified = false;\r
+       public boolean removed = false;\r
+       \r
+       public Link(ILinkType type, Resource domainElement, Object rangeElement) {\r
+               this.type = type;\r
+               this.domainElement = domainElement;\r
+               this.rangeElement = rangeElement;               \r
+       }               \r
+}
\ No newline at end of file
diff --git a/org.simantics.objmap/src/org/simantics/objmap/impl/Mapping.java b/org.simantics.objmap/src/org/simantics/objmap/impl/Mapping.java
new file mode 100644 (file)
index 0000000..fb31e2c
--- /dev/null
@@ -0,0 +1,411 @@
+package org.simantics.objmap.impl;\r
+\r
+import gnu.trove.THashMap;\r
+import gnu.trove.TObjectIdentityHashingStrategy;\r
+\r
+import java.util.AbstractSet;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.Set;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.ILinkType;\r
+import org.simantics.objmap.IMapping;\r
+import org.simantics.objmap.IMappingListener;\r
+import org.simantics.objmap.IMappingSchema;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * An implementation of IMapping. The class should not be created\r
+ * directly but using methods in Mappings.\r
+ * @see org.simantics.objmap.Mappings\r
+ * @author Hannu Niemistö\r
+ */\r
+public class Mapping implements IMapping {\r
+\r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       private static final TObjectIdentityHashingStrategy<Object> IDENTITY_HASHING =\r
+               new TObjectIdentityHashingStrategy<Object>();\r
+       \r
+       IMappingSchema schema;\r
+       \r
+       THashMap<Resource, Link> domain = new THashMap<Resource, Link>();\r
+       THashMap<Object, Link> range = new THashMap<Object, Link>(IDENTITY_HASHING);\r
+       ArrayList<IMappingListener> listeners = new ArrayList<IMappingListener>();\r
+       \r
+       ArrayList<Link> modifiedDomainLinks = new ArrayList<Link>();\r
+       ArrayList<Link> modifiedRangeLinks = new ArrayList<Link>();\r
+       boolean disposed = false;\r
+       \r
+       boolean listensDomain; \r
+       \r
+       public Mapping(IMappingSchema schema, boolean listensDomain) {\r
+               this.schema = schema;\r
+               this.listensDomain = listensDomain;\r
+       }\r
+\r
+       private void removeLink(Link link) {\r
+               if(link.domainModified)\r
+                       modifiedDomainLinks.remove(link);\r
+               if(link.rangeModified)\r
+                       modifiedRangeLinks.remove(link);\r
+               link.removed = true;\r
+       }\r
+       \r
+       private void createDomain(WriteGraph g, Link link) throws MappingException {\r
+               ILinkType type = schema.linkTypeOfRangeElement(link.rangeElement);\r
+               Resource domainElement = type.createDomainElement(g, link.rangeElement);\r
+               \r
+               link.type = type;\r
+               link.domainElement = domainElement;\r
+               domain.put(domainElement, link);\r
+       }\r
+       \r
+       private void createRange(ReadGraph g, Link link) throws MappingException {\r
+               ILinkType type = schema.linkTypeOfDomainElement(g, link.domainElement);         \r
+               Object rangeElement = type.createRangeElement(g, link.domainElement);\r
+               \r
+               link.type = type;\r
+               link.rangeElement = rangeElement;\r
+               range.put(rangeElement, link);\r
+       }\r
+       \r
+       Set<Resource> domainSet = new AbstractSet<Resource>() {\r
+\r
+               public boolean add(Resource e) {\r
+                       if(domain.containsKey(e))\r
+                               return false;\r
+                       Link link = new Link(null, e, null);\r
+                       domain.put(e, link);\r
+                       modifiedDomainLinks.add(link);\r
+                       return true;\r
+               }\r
+               \r
+               public boolean contains(Object o) {\r
+                       return domain.contains(o);\r
+               }\r
+               \r
+               public boolean remove(Object o) {\r
+                       Link link = domain.remove(o);                   \r
+                       if(link == null)\r
+                               return false;\r
+                       removeLink(link);\r
+                       if(link.rangeElement != null)\r
+                               range.remove(link.rangeElement);\r
+                       return true;    \r
+               }\r
+               \r
+               @Override\r
+               public Iterator<Resource> iterator() {\r
+                   // FIXME does not implement Iterator.remove correctly\r
+                       return domain.keySet().iterator();\r
+               }\r
+\r
+               @Override\r
+               public int size() {\r
+                       return domain.size();\r
+               }\r
+               \r
+       };\r
+       \r
+       Set<Object> rangeSet = new AbstractSet<Object>() {\r
+\r
+               public boolean add(Object e) {\r
+                       if(range.containsKey(e))\r
+                               return false;\r
+                       Link link = new Link(null, null, e);\r
+                       range.put(e, link);\r
+                       modifiedRangeLinks.add(link);\r
+                       return true;\r
+               }\r
+               \r
+               public boolean contains(Object o) {\r
+                       return range.contains(o);\r
+               }\r
+               \r
+               public boolean remove(Object o) {\r
+                       Link link = range.remove(o);                    \r
+                       if(link == null)\r
+                               return false;\r
+                       removeLink(link);\r
+                       if(link.domainElement != null)\r
+                               domain.remove(link.domainElement);\r
+                       return true;\r
+               }\r
+               \r
+               @Override\r
+               public Iterator<Object> iterator() {\r
+                   // FIXME does not implement Iterator.remove correctly\r
+                       return range.keySet().iterator();\r
+               }\r
+\r
+               @Override\r
+               public int size() {\r
+                       return range.size();\r
+               }\r
+               \r
+       };\r
+       \r
+       class DomainToRange implements IFunction<Resource, Object> {\r
+\r
+               ReadGraph g;\r
+               \r
+               public DomainToRange(ReadGraph g) {\r
+                       this.g = g;\r
+               }\r
+\r
+               @Override\r
+               public Object get(Resource element) throws MappingException {\r
+                       Link link = domain.get(element);\r
+                       if(link == null) {\r
+                               ILinkType type = schema.linkTypeOfDomainElement(g, element);\r
+                               Object rangeElement = type.createRangeElement(g, element);\r
+                               \r
+                               link = new Link(type, element, rangeElement);\r
+                               domain.put(element, link);                      \r
+                               range.put(rangeElement, link);\r
+                               link.domainModified = true;\r
+                               modifiedDomainLinks.add(link);\r
+                               \r
+                               return rangeElement;                            \r
+                       }\r
+                       else {\r
+                               if(link.type == null) \r
+                                       createRange(g, link);\r
+                               return link.rangeElement;\r
+                       }\r
+               }\r
+               \r
+       };\r
+       \r
+       class RangeToDomain implements IFunction<Object, Resource> {\r
+\r
+               WriteGraph g;\r
+               \r
+               public RangeToDomain(WriteGraph g) {\r
+                       this.g = g;\r
+               }\r
+               \r
+               @Override\r
+               public Resource get(Object element) throws MappingException {\r
+                       Link link = range.get(element);\r
+                       if(link == null) {\r
+                               ILinkType type = schema.linkTypeOfRangeElement(element); \r
+                               Resource domainElement = type.createDomainElement(g, element);\r
+                               \r
+                               link = new Link(type, domainElement, element);\r
+                               domain.put(domainElement, link);                        \r
+                               range.put(element, link);\r
+                               link.rangeModified = true;\r
+                               modifiedRangeLinks.add(link);\r
+                               \r
+                               return domainElement;                           \r
+                       }\r
+                       else {\r
+                               if(link.type == null)\r
+                                       createDomain(g, link);\r
+                               return link.domainElement;\r
+                       }\r
+               }\r
+               \r
+       };\r
+       \r
+       @Override\r
+       public Set<Resource> getDomain() {\r
+               return domainSet;\r
+       }\r
+       \r
+       @Override\r
+       public Set<Object> getRange() {\r
+               return rangeSet;\r
+       }\r
+       \r
+       @Override\r
+       public Collection<Resource> updateDomain(WriteGraph g) throws MappingException {\r
+           LOGGER.info("Mapping.updateDomain");\r
+               RangeToDomain map = new RangeToDomain(g);\r
+               ArrayList<Resource> updated = new ArrayList<Resource>();\r
+               while(!modifiedRangeLinks.isEmpty()) {\r
+                   LOGGER.info("    modifiedRangeLinks.size() = " + modifiedRangeLinks.size());\r
+                   \r
+                       Link link = modifiedRangeLinks.remove(modifiedRangeLinks.size()-1);\r
+                       link.rangeModified = false;\r
+                       if(link.domainModified) {\r
+                               link.domainModified = false;\r
+                               modifiedDomainLinks.remove(link);\r
+                       }\r
+                       \r
+                       if(link.type == null) {\r
+                               createDomain(g, link);\r
+                       }\r
+                       \r
+                       if(link.type.updateDomain(g, map, link.domainElement, link.rangeElement))\r
+                               updated.add(link.domainElement);\r
+               }               \r
+               return updated;\r
+       }\r
+       \r
+       @Override\r
+       public Collection<Object> updateRange(ReadGraph g) throws MappingException {\r
+           LOGGER.info("Mapping.updateRange");\r
+               DomainToRange map = new DomainToRange(g);\r
+               ArrayList<Object> updated = new ArrayList<Object>();\r
+               while(!modifiedDomainLinks.isEmpty()) {\r
+                       Link link = modifiedDomainLinks.remove(modifiedDomainLinks.size()-1);\r
+                       link.domainModified = false;\r
+                       if(link.rangeModified) {\r
+                               link.rangeModified = false;\r
+                               modifiedRangeLinks.remove(link);\r
+                       }\r
+                       \r
+                       if(link.type == null) {\r
+                               createRange(g, link);\r
+                       }\r
+                       \r
+                       if(listensDomain) {\r
+                           RangeUpdateRequest request = new RangeUpdateRequest(link, map, this);\r
+                           g.syncRequest(request, request);\r
+                           // TODO check if really modified\r
+                           updated.add(link.rangeElement);\r
+                       }\r
+                       else\r
+                           if(link.type.updateRange(g, map, link.domainElement, link.rangeElement))\r
+                               updated.add(link.rangeElement);\r
+               }       \r
+               return updated;\r
+       }\r
+\r
+       @Override\r
+       public Object get(Resource domainElement) {\r
+               Link link = domain.get(domainElement);\r
+               if(link == null)\r
+                       return null;\r
+               return link.rangeElement;\r
+       }\r
+\r
+       @Override\r
+       public Resource inverseGet(Object rangeElement) {\r
+               Link link = range.get(rangeElement);\r
+               if(link == null)\r
+                       return null;\r
+               return link.domainElement;\r
+       }\r
+\r
+       @Override\r
+       public Resource inverseMap(WriteGraph g, Object rangeElement) throws MappingException {\r
+               getRange().add(rangeElement);\r
+               updateDomain(g);\r
+               return inverseGet(rangeElement);\r
+       }\r
+\r
+       @Override\r
+       public Object map(ReadGraph g, Resource domainElement) throws MappingException {\r
+               getDomain().add(domainElement);\r
+               updateRange(g);\r
+               return get(domainElement);\r
+       }\r
+\r
+       void domainModified(Link link) {\r
+           if(!link.domainModified) {\r
+            link.domainModified = true;\r
+            modifiedDomainLinks.add(link);\r
+            if(modifiedDomainLinks.size() == 1) {\r
+                for(IMappingListener listener : listeners)\r
+                    listener.domainModified();\r
+            }\r
+        }\r
+       }\r
+       \r
+       @Override\r
+       public void domainModified(Resource domainElement) {\r
+               Link link = domain.get(domainElement);\r
+               if(link != null)\r
+                   domainModified(link);\r
+       }\r
+\r
+       void rangeModified(Link link) {\r
+           if(!link.rangeModified) {\r
+            link.rangeModified = true;\r
+            modifiedRangeLinks.add(link);\r
+            if(modifiedRangeLinks.size() == 1) {\r
+                for(IMappingListener listener : listeners)\r
+                    listener.rangeModified();\r
+            }\r
+        }\r
+       }\r
+       \r
+       @Override\r
+       public void rangeModified(Object rangeElement) {\r
+               Link link = range.get(rangeElement);\r
+               if(link != null)\r
+                   rangeModified(link);\r
+       }\r
+\r
+       @Override\r
+       public boolean isDomainModified() {\r
+               return !modifiedDomainLinks.isEmpty();\r
+       }\r
+\r
+       @Override\r
+       public boolean isRangeModified() {\r
+               return !modifiedRangeLinks.isEmpty();\r
+       }\r
+\r
+       @Override\r
+       public void addMappingListener(IMappingListener listener) {\r
+               listeners.add(listener);\r
+       }\r
+\r
+       @Override\r
+       public void removeMappingListener(IMappingListener listener) {\r
+               listeners.remove(listener);             \r
+       }\r
+\r
+       @Override\r
+       public Collection<Resource> getConflictingDomainElements() {\r
+               ArrayList<Resource> result = new ArrayList<Resource>();\r
+               if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {\r
+                       for(Link link : modifiedDomainLinks)\r
+                               if(link.rangeModified)\r
+                                       result.add(link.domainElement);\r
+               }\r
+               else {\r
+                       for(Link link : modifiedRangeLinks)\r
+                               if(link.domainModified)\r
+                                       result.add(link.domainElement);\r
+               }\r
+               return result;\r
+       }\r
+\r
+       @Override\r
+       public Collection<Object> getConflictingRangeElements() {\r
+               ArrayList<Object> result = new ArrayList<Object>();\r
+               if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {\r
+                       for(Link link : modifiedDomainLinks)\r
+                               if(link.rangeModified)\r
+                                       result.add(link.rangeElement);\r
+               }\r
+               else {\r
+                       for(Link link : modifiedRangeLinks)\r
+                               if(link.domainModified)\r
+                                       result.add(link.rangeElement);\r
+               }\r
+               return result;\r
+       }\r
+\r
+    @Override\r
+    public void dispose() {\r
+        disposed = true;\r
+    }\r
+    \r
+    public boolean isDisposed() {\r
+        return disposed;\r
+    }\r
+       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/impl/RangeUpdateRequest.java b/org.simantics.objmap/src/org/simantics/objmap/impl/RangeUpdateRequest.java
new file mode 100644 (file)
index 0000000..b7578f8
--- /dev/null
@@ -0,0 +1,64 @@
+package org.simantics.objmap.impl;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.procedure.SyncListener;\r
+import org.simantics.db.request.Read;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.MappingException;\r
+\r
+public class RangeUpdateRequest implements Read<Boolean>, SyncListener<Boolean> {\r
+\r
+    Link link;\r
+    /*\r
+     * Note that this map uses a read request that it has got from caller and \r
+     * not the one that is used in updateRange. This is intentional.\r
+     */\r
+    IFunction<Resource, Object> map; // map==null is used to flag that request is performed once\r
+    Mapping mapping; // mapping==null is used as a flag the request disposed\r
+    \r
+    public RangeUpdateRequest(Link link, IFunction<Resource, Object> map, Mapping mapping) {\r
+        this.link = link;\r
+        this.map = map;\r
+        this.mapping = mapping;\r
+    }\r
+\r
+    @Override\r
+    public Boolean perform(ReadGraph g) throws DatabaseException {\r
+        if(map != null) {\r
+            link.type.updateRange(g, map, link.domainElement, link.rangeElement);\r
+            map = null;\r
+            return Boolean.TRUE;\r
+        }\r
+        else if(mapping != null) {\r
+            mapping.domainModified(link);\r
+            mapping = null;\r
+            return Boolean.FALSE;\r
+        }\r
+        else\r
+            return null;\r
+    }\r
+    \r
+    @Override\r
+    public void exception(ReadGraph graph, Throwable throwable)\r
+            throws DatabaseException {\r
+        if(throwable instanceof DatabaseException)\r
+            throw (DatabaseException)throwable;\r
+        else\r
+            throw new MappingException(throwable);\r
+    }\r
+\r
+    @Override\r
+    public void execute(ReadGraph graph, Boolean result)\r
+            throws DatabaseException {       \r
+    }\r
+\r
+    @Override\r
+    public boolean isDisposed() {\r
+        return mapping == null || link.removed || mapping.isDisposed();\r
+    }\r
+    \r
+    \r
+    \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/impl/UnidirectionalMapping.java b/org.simantics.objmap/src/org/simantics/objmap/impl/UnidirectionalMapping.java
new file mode 100644 (file)
index 0000000..79395db
--- /dev/null
@@ -0,0 +1,254 @@
+package org.simantics.objmap.impl;\r
+\r
+import gnu.trove.THashMap;\r
+import gnu.trove.TObjectIdentityHashingStrategy;\r
+\r
+import java.util.AbstractSet;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.Set;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.ILinkType;\r
+import org.simantics.objmap.IMapping;\r
+import org.simantics.objmap.IMappingListener;\r
+import org.simantics.objmap.IMappingSchema;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * An implementation of IMapping. The class should not be created\r
+ * directly but using methods in Mappings.\r
+ * @see org.simantics.objmap.Mappings\r
+ * @author Hannu Niemistö\r
+ */\r
+public class UnidirectionalMapping implements IMapping {\r
+\r
+       private static final TObjectIdentityHashingStrategy<Object> IDENTITY_HASHING =\r
+               new TObjectIdentityHashingStrategy<Object>();\r
+       \r
+       IMappingSchema schema;\r
+       \r
+       THashMap<Resource, Link> domain = new THashMap<Resource, Link>();\r
+       ArrayList<IMappingListener> listeners = new ArrayList<IMappingListener>();\r
+       \r
+       ArrayList<Link> modifiedDomainLinks = new ArrayList<Link>();\r
+       boolean disposed = false;\r
+       \r
+       public UnidirectionalMapping(IMappingSchema schema) {\r
+               this.schema = schema;\r
+       }\r
+\r
+       private void removeLink(Link link) {\r
+               if(link.domainModified)\r
+                       modifiedDomainLinks.remove(link);\r
+               link.removed = true;\r
+       }\r
+       \r
+       private void createDomain(WriteGraph g, Link link) throws MappingException {\r
+               ILinkType type = schema.linkTypeOfRangeElement(link.rangeElement);\r
+               Resource domainElement = type.createDomainElement(g, link.rangeElement);\r
+               \r
+               link.type = type;\r
+               link.domainElement = domainElement;\r
+               domain.put(domainElement, link);\r
+       }\r
+       \r
+       private void createRange(ReadGraph g, Link link) throws MappingException {\r
+               ILinkType type = schema.linkTypeOfDomainElement(g, link.domainElement);         \r
+               Object rangeElement = type.createRangeElement(g, link.domainElement);\r
+               \r
+               link.type = type;\r
+               link.rangeElement = rangeElement;\r
+       }\r
+       \r
+       Set<Resource> domainSet = new AbstractSet<Resource>() {\r
+\r
+               public boolean add(Resource e) {\r
+                       if(domain.containsKey(e))\r
+                               return false;\r
+                       Link link = new Link(null, e, null);\r
+                       domain.put(e, link);\r
+                       modifiedDomainLinks.add(link);\r
+                       return true;\r
+               }\r
+               \r
+               public boolean contains(Object o) {\r
+                       return domain.contains(o);\r
+               }\r
+               \r
+               public boolean remove(Object o) {\r
+                       Link link = domain.remove(o);                   \r
+                       if(link == null)\r
+                               return false;\r
+                       removeLink(link);\r
+                       return true;    \r
+               }\r
+               \r
+               @Override\r
+               public Iterator<Resource> iterator() {\r
+                   // FIXME does not implement Iterator.remove correctly\r
+                       return domain.keySet().iterator();\r
+               }\r
+\r
+               @Override\r
+               public int size() {\r
+                       return domain.size();\r
+               }\r
+               \r
+       };\r
+       \r
+       class DomainToRange implements IFunction<Resource, Object> {\r
+\r
+               ReadGraph g;\r
+               \r
+               public DomainToRange(ReadGraph g) {\r
+                       this.g = g;\r
+               }\r
+\r
+               @Override\r
+               public Object get(Resource element) throws MappingException {\r
+                       Link link = domain.get(element);\r
+                       if(link == null) {\r
+                               ILinkType type = schema.linkTypeOfDomainElement(g, element);\r
+                               Object rangeElement = type.createRangeElement(g, element);\r
+                               \r
+                               link = new Link(type, element, rangeElement);\r
+                               domain.put(element, link);              \r
+                               link.domainModified = true;\r
+                               modifiedDomainLinks.add(link);\r
+                               \r
+                               return rangeElement;                            \r
+                       }\r
+                       else {\r
+                               if(link.type == null) \r
+                                       createRange(g, link);\r
+                               return link.rangeElement;\r
+                       }\r
+               }\r
+               \r
+       };\r
+       \r
+       @Override\r
+       public Set<Resource> getDomain() {\r
+               return domainSet;\r
+       }\r
+       \r
+       @Override\r
+       public Set<Object> getRange() {\r
+               throw new UnsupportedOperationException();\r
+       }\r
+       \r
+       @Override\r
+       public Collection<Resource> updateDomain(WriteGraph g) throws MappingException {\r
+               throw new UnsupportedOperationException();\r
+       }\r
+       \r
+       @Override\r
+       public Collection<Object> updateRange(ReadGraph g) throws MappingException {\r
+               DomainToRange map = new DomainToRange(g);\r
+               ArrayList<Object> updated = new ArrayList<Object>();\r
+               while(!modifiedDomainLinks.isEmpty()) {\r
+                       Link link = modifiedDomainLinks.remove(modifiedDomainLinks.size()-1);\r
+                       link.domainModified = false;                    \r
+                       if(link.type == null) {\r
+                               createRange(g, link);\r
+                       }\r
+                       \r
+                       link.type.updateRange(g, map, link.domainElement, link.rangeElement);\r
+                       updated.add(link.rangeElement);\r
+               }       \r
+               return updated;\r
+       }\r
+\r
+       @Override\r
+       public Object get(Resource domainElement) {\r
+               Link link = domain.get(domainElement);\r
+               if(link == null)\r
+                       return null;\r
+               return link.rangeElement;\r
+       }\r
+\r
+       @Override\r
+       public Resource inverseGet(Object rangeElement) {\r
+               throw new UnsupportedOperationException();\r
+       }\r
+\r
+       @Override\r
+       public Resource inverseMap(WriteGraph g, Object rangeElement) throws MappingException {\r
+           throw new UnsupportedOperationException();\r
+       }\r
+\r
+       @Override\r
+       public Object map(ReadGraph g, Resource domainElement) throws MappingException {\r
+               getDomain().add(domainElement);\r
+               updateRange(g);\r
+               return get(domainElement);\r
+       }\r
+\r
+       void domainModified(Link link) {\r
+           if(!link.domainModified) {\r
+            link.domainModified = true;\r
+            modifiedDomainLinks.add(link);\r
+            if(modifiedDomainLinks.size() == 1) {\r
+                for(IMappingListener listener : listeners)\r
+                    listener.domainModified();\r
+            }\r
+        }\r
+       }\r
+       \r
+       @Override\r
+       public void domainModified(Resource domainElement) {\r
+               Link link = domain.get(domainElement);\r
+               if(link != null)\r
+                   domainModified(link);\r
+       }\r
+\r
+       @Override\r
+       public boolean isDomainModified() {\r
+               return !modifiedDomainLinks.isEmpty();\r
+       }\r
+\r
+       @Override\r
+       public boolean isRangeModified() {\r
+               throw new UnsupportedOperationException();\r
+       }\r
+\r
+       @Override\r
+       public void addMappingListener(IMappingListener listener) {\r
+               listeners.add(listener);\r
+       }\r
+\r
+       @Override\r
+       public void removeMappingListener(IMappingListener listener) {\r
+               listeners.remove(listener);             \r
+       }\r
+\r
+       @Override\r
+       public Collection<Resource> getConflictingDomainElements() {\r
+           throw new UnsupportedOperationException();\r
+       }\r
+\r
+       @Override\r
+       public Collection<Object> getConflictingRangeElements() {\r
+           throw new UnsupportedOperationException();\r
+       }\r
+\r
+    @Override\r
+    public void dispose() {\r
+        disposed = true;\r
+    }\r
+    \r
+    public boolean isDisposed() {\r
+        return disposed;\r
+    }\r
+\r
+    @Override\r
+    public void rangeModified(Object rangeElement) {\r
+        throw new UnsupportedOperationException();\r
+    }\r
+       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/MappedElementRule.java b/org.simantics.objmap/src/org/simantics/objmap/rules/MappedElementRule.java
new file mode 100644 (file)
index 0000000..51a3e6e
--- /dev/null
@@ -0,0 +1,55 @@
+package org.simantics.objmap.rules;\r
+\r
+import java.util.ArrayList;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.MappingException;\r
+import org.simantics.objmap.rules.domain.IDomainAccessor;\r
+import org.simantics.objmap.rules.range.IRangeAccessor;\r
+\r
+/**\r
+ * A rule that synchronizes collection of elements between\r
+ * domain and range accessors. Elements are mapped from\r
+ * between domain and range during the synchronization.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class MappedElementRule implements IMappingRule {\r
+    \r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       IDomainAccessor<Resource> domainAccessor;\r
+       IRangeAccessor<Object> rangeAccessor;\r
+       \r
+       public MappedElementRule(IDomainAccessor<Resource> domainAccessor,\r
+            IRangeAccessor<Object> rangeAccessor) {\r
+        this.domainAccessor = domainAccessor;\r
+        this.rangeAccessor = rangeAccessor;\r
+    }\r
+\r
+    @Override\r
+       public boolean updateDomain(WriteGraph g, IFunction<Object, Resource> map,\r
+                       Resource domainElement, Object rangeElement)\r
+                       throws MappingException {\r
+        LOGGER.info("    MappedElementRule.updateDomain");\r
+           Object value = rangeAccessor.get(rangeElement);\r
+           Resource mappedValue = value == null ? null : map.get(value);\r
+               return domainAccessor.set(g, domainElement, mappedValue);\r
+       }\r
+\r
+       @Override\r
+       public boolean updateRange(ReadGraph g, IFunction<Resource, Object> map,\r
+                       Resource domainElement, Object rangeElement)\r
+                       throws MappingException {\r
+        LOGGER.info("    MappedElementRule.updateRange");   \r
+           Resource value = domainAccessor.get(g, domainElement);\r
+        Object mappedValue = value == null ? null : map.get(value);\r
+        return rangeAccessor.set(rangeElement, mappedValue);\r
+       }       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/MappedElementsRule.java b/org.simantics.objmap/src/org/simantics/objmap/rules/MappedElementsRule.java
new file mode 100644 (file)
index 0000000..e37ce13
--- /dev/null
@@ -0,0 +1,60 @@
+package org.simantics.objmap.rules;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.MappingException;\r
+import org.simantics.objmap.rules.domain.IDomainAccessor;\r
+import org.simantics.objmap.rules.range.IRangeAccessor;\r
+\r
+/**\r
+ * A rule that synchronizes collection of elements between\r
+ * domain and range accessors. Elements are mapped from\r
+ * between domain and range during the synchronization.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class MappedElementsRule implements IMappingRule {\r
+    \r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       IDomainAccessor<Collection<Resource>> domainAccessor;\r
+       IRangeAccessor<Collection<Object>> rangeAccessor;\r
+       \r
+       public MappedElementsRule(IDomainAccessor<Collection<Resource>> domainAccessor,\r
+                       IRangeAccessor<Collection<Object>> rangeAccessor) {\r
+               this.domainAccessor = domainAccessor;\r
+               this.rangeAccessor = rangeAccessor;\r
+       }\r
+\r
+       @Override\r
+       public boolean updateDomain(WriteGraph g, IFunction<Object, Resource> map,\r
+                       Resource domainElement, Object rangeElement)\r
+                       throws MappingException {\r
+           LOGGER.info("    MappedElementsRule.updateDomain");\r
+           Collection<Object> value = rangeAccessor.get(rangeElement);\r
+           ArrayList<Resource> mappedValue = new ArrayList<Resource>(value.size());\r
+           for(Object obj : value)\r
+               mappedValue.add(map.get(obj));\r
+               return domainAccessor.set(g, domainElement, mappedValue);\r
+       }\r
+\r
+       @Override\r
+       public boolean updateRange(ReadGraph g, IFunction<Resource, Object> map,\r
+                       Resource domainElement, Object rangeElement)\r
+                       throws MappingException {\r
+           LOGGER.info("    MappedElementsRule.updateRange");\r
+           Collection<Resource> value = domainAccessor.get(g, domainElement);\r
+        ArrayList<Object> mappedValue = new ArrayList<Object>(value.size());\r
+        for(Resource r : value)\r
+            mappedValue.add(map.get(r));\r
+        return rangeAccessor.set(rangeElement, mappedValue);\r
+       }       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/ValueRule.java b/org.simantics.objmap/src/org/simantics/objmap/rules/ValueRule.java
new file mode 100644 (file)
index 0000000..13e4670
--- /dev/null
@@ -0,0 +1,52 @@
+package org.simantics.objmap.rules;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.MappingException;\r
+import org.simantics.objmap.rules.domain.IDomainAccessor;\r
+import org.simantics.objmap.rules.range.IRangeAccessor;\r
+\r
+/**\r
+ * A rule that synchronizes values between domain and\r
+ * range accessors.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class ValueRule implements IMappingRule {\r
+    \r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       IDomainAccessor<Object> domainAccessor;\r
+       IRangeAccessor<Object> rangeAccessor;\r
+       \r
+       public ValueRule(IDomainAccessor<Object> domainAccessor,\r
+                       IRangeAccessor<Object> rangeAccessor) {\r
+               this.domainAccessor = domainAccessor;\r
+               this.rangeAccessor = rangeAccessor;\r
+       }\r
+\r
+       @Override\r
+       public boolean updateDomain(WriteGraph g, IFunction<Object, Resource> map,\r
+                       Resource domainElement, Object rangeElement)\r
+                       throws MappingException {\r
+        LOGGER.info("    ValueRule.updateDomain");         \r
+               Object value = rangeAccessor.get(rangeElement);\r
+               return domainAccessor.set(g, domainElement, value);\r
+       }\r
+\r
+       @Override\r
+       public boolean updateRange(ReadGraph g, IFunction<Resource, Object> map,\r
+                       Resource domainElement, Object rangeElement)\r
+                       throws MappingException {\r
+        LOGGER.info("    ValueRule.updateRange");\r
+               Object value = domainAccessor.get(g, domainElement);\r
+               return rangeAccessor.set(rangeElement, value);\r
+       }       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/domain/IDomainAccessor.java b/org.simantics.objmap/src/org/simantics/objmap/rules/domain/IDomainAccessor.java
new file mode 100644 (file)
index 0000000..abb50e7
--- /dev/null
@@ -0,0 +1,15 @@
+package org.simantics.objmap.rules.domain;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * Provides access to some property of domain elements.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IDomainAccessor<T> {\r
+       T get(ReadGraph g, Resource element) throws MappingException;\r
+       boolean set(WriteGraph g, Resource element, T value) throws MappingException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/domain/MappingUtils.java b/org.simantics.objmap/src/org/simantics/objmap/rules/domain/MappingUtils.java
new file mode 100644 (file)
index 0000000..2a6ef5b
--- /dev/null
@@ -0,0 +1,74 @@
+package org.simantics.objmap.rules.domain;\r
+\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+/**\r
+ * Static utility methods for rule implementations.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class MappingUtils {\r
+\r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+    /**\r
+     * Adds and removes statements to/from the database so that <code>objects</code>\r
+     * will be exactly the objects connected to <code>subject</code> by <code>predicate</code>.\r
+     * Returns true if the method made modifications to the database.\r
+     */\r
+       public static boolean synchronizeStatements(WriteGraph g, Resource subject, Resource predicate, Resource[] objects) \r
+               throws DatabaseException {\r
+               Collection<Resource> currentObjects0 = g.getObjects(subject, predicate);\r
+               Resource[] currentObjects = currentObjects0.toArray(new Resource[currentObjects0.size()]);\r
+               \r
+               Arrays.sort(objects);\r
+               Arrays.sort(currentObjects);\r
+               \r
+               boolean modified = false;\r
+               int i=0, j=0;   \r
+               if(currentObjects.length > 0 && objects.length > 0)\r
+               while(true) {\r
+                       int cmp = currentObjects[i].compareTo(objects[j]);\r
+                       if(cmp < 0) {\r
+                           LOGGER.info("            remove statement");\r
+                               g.deny(subject, predicate, currentObjects[i]);                          \r
+                               modified = true;\r
+                               ++i;\r
+                               if(i >= currentObjects.length)\r
+                                       break;\r
+                       }\r
+                       else if(cmp > 0) {\r
+                           LOGGER.info("            add statement");\r
+                               g.claim(subject, predicate, objects[j]);\r
+                               modified = true;\r
+                               ++j;\r
+                               if(j >= objects.length)\r
+                                       break;\r
+                       }\r
+                       else {\r
+                               ++i; ++j;\r
+                               if(i >= currentObjects.length)\r
+                                       break;\r
+                               if(j >= objects.length)\r
+                                       break;\r
+                       }\r
+               }\r
+               while(i < currentObjects.length) {\r
+                       g.deny(subject, predicate, currentObjects[i]);\r
+                       modified = true;\r
+                       ++i;\r
+               }\r
+               while(j < objects.length) {\r
+                       g.claim(subject, predicate, objects[j]);\r
+                       modified = true;\r
+                       ++j;\r
+               }\r
+               return modified;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedObjectAccessor.java b/org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedObjectAccessor.java
new file mode 100644 (file)
index 0000000..5dfdaef
--- /dev/null
@@ -0,0 +1,60 @@
+package org.simantics.objmap.rules.domain;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * Accesses a resource attached to the element by given functional relation.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class RelatedObjectAccessor implements IDomainAccessor<Resource> {\r
+\r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       Resource relation;\r
+       \r
+       public RelatedObjectAccessor(Resource relation) {\r
+               this.relation = relation;\r
+       }\r
+\r
+       @Override\r
+       public Resource get(ReadGraph g, Resource element) throws MappingException {\r
+               try {\r
+                   LOGGER.info("        RelatedObjectAccessor.get");\r
+                       return g.getPossibleObject(element, relation);\r
+               } catch (DatabaseException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public boolean set(WriteGraph g, Resource element, Resource value)\r
+                       throws MappingException {\r
+               try {\r
+                   LOGGER.info("        RelatedObjectAccessor.set");\r
+                   Resource resource = g.getPossibleObject(element, relation);\r
+                       if(resource == null) {\r
+                           if(value == null)\r
+                               return false;\r
+                           g.claim(element, relation, value);\r
+                           return true;\r
+                       }\r
+                       else if(resource.equals(value))\r
+                           return false;\r
+                       else {\r
+                           g.deny(element, relation);\r
+                           if(value != null)\r
+                               g.claim(element, relation, value);\r
+                           return true;\r
+                       }\r
+               } catch (DatabaseException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+               \r
+       }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedObjectsAccessor.java b/org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedObjectsAccessor.java
new file mode 100644 (file)
index 0000000..9ea6573
--- /dev/null
@@ -0,0 +1,48 @@
+package org.simantics.objmap.rules.domain;\r
+\r
+import java.util.Collection;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * Accesses the set of objects attached to the element by the given relation.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class RelatedObjectsAccessor implements IDomainAccessor<Collection<Resource>> {\r
+\r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       Resource relation;\r
+\r
+       public RelatedObjectsAccessor(Resource relation) {\r
+               this.relation = relation;\r
+       }\r
+\r
+       @Override\r
+       public Collection<Resource> get(ReadGraph g, Resource element) throws MappingException {\r
+               try {\r
+                   LOGGER.info("        RelatedObjectsAccessor.get");\r
+                       return g.getObjects(element, relation);\r
+               } catch (DatabaseException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public boolean set(WriteGraph g, Resource element, Collection<Resource> value)\r
+                       throws MappingException {\r
+               try {\r
+                   LOGGER.info("        RelatedObjectsAccessor.set");\r
+                       return MappingUtils.synchronizeStatements(g, element, relation, value.toArray(new Resource[value.size()]));\r
+               } catch (DatabaseException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+               \r
+       }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedValueAccessor.java b/org.simantics.objmap/src/org/simantics/objmap/rules/domain/RelatedValueAccessor.java
new file mode 100644 (file)
index 0000000..5cad1b8
--- /dev/null
@@ -0,0 +1,72 @@
+package org.simantics.objmap.rules.domain;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * Accesses a value attached to the element by given functional relation.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class RelatedValueAccessor implements IDomainAccessor<Object> {\r
+\r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       Resource relation;\r
+       Resource valueType;\r
+\r
+       public RelatedValueAccessor(Resource relation, Resource valueType) {\r
+               this.relation = relation;\r
+               this.valueType = valueType;\r
+       }\r
+\r
+       @Override\r
+       public Object get(ReadGraph g, Resource element) throws MappingException {\r
+               try {\r
+                   LOGGER.info("        RelatedValueAccessor.get");\r
+                       Resource valueResource = g.getPossibleObject(element, relation);\r
+                       if(valueResource == null)\r
+                               return null;\r
+                       return g.getValue(valueResource);\r
+               } catch (DatabaseException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public boolean set(WriteGraph g, Resource element, Object value)\r
+                       throws MappingException {\r
+               try {\r
+                   LOGGER.info("        RelatedValueAccessor.set");\r
+                       Resource valueResource = g.getPossibleObject(element, relation);\r
+                       if(valueResource == null) {\r
+                               if(value == null)\r
+                                       return false;\r
+                               valueResource = g.newResource();\r
+                               g.claim(valueResource, g.getBuiltins().InstanceOf, \r
+                                               valueType);\r
+                               g.claim(element, relation, valueResource);\r
+                               g.claimValue(valueResource, value);                             \r
+                               return true;\r
+                       }\r
+                       else {\r
+                               if(value == null) {\r
+                                       g.deny(valueResource);\r
+                                       return true;\r
+                               }                               \r
+                               Object currentValue = g.getValue(valueResource);\r
+                               if(currentValue.equals(value))\r
+                                       return false;\r
+                               g.claimValue(valueResource, value);\r
+                               return true;\r
+                       }\r
+               } catch (DatabaseException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+               \r
+       }\r
+\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/factory/IClassRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/rules/factory/IClassRuleFactory.java
new file mode 100644 (file)
index 0000000..20fcce6
--- /dev/null
@@ -0,0 +1,11 @@
+package org.simantics.objmap.rules.factory;\r
+\r
+import java.lang.annotation.Annotation;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.IMappingRule;\r
+\r
+public interface IClassRuleFactory {\r
+    IMappingRule create(ReadGraph g, Annotation annotation, Class<?> clazz) throws DatabaseException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/factory/IFieldRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/rules/factory/IFieldRuleFactory.java
new file mode 100644 (file)
index 0000000..8e95a06
--- /dev/null
@@ -0,0 +1,12 @@
+package org.simantics.objmap.rules.factory;\r
+\r
+import java.lang.annotation.Annotation;\r
+import java.lang.reflect.Field;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.IMappingRule;\r
+\r
+public interface IFieldRuleFactory {\r
+    IMappingRule create(ReadGraph g, Annotation annotation, Field field) throws DatabaseException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/factory/IMethodRuleFactory.java b/org.simantics.objmap/src/org/simantics/objmap/rules/factory/IMethodRuleFactory.java
new file mode 100644 (file)
index 0000000..61d26a9
--- /dev/null
@@ -0,0 +1,12 @@
+package org.simantics.objmap.rules.factory;\r
+\r
+import java.lang.annotation.Annotation;\r
+import java.lang.reflect.Method;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.IMappingRule;\r
+\r
+public interface IMethodRuleFactory {\r
+    IMappingRule create(ReadGraph g, Annotation annotation, Method method) throws DatabaseException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/range/FieldAccessor.java b/org.simantics.objmap/src/org/simantics/objmap/rules/range/FieldAccessor.java
new file mode 100644 (file)
index 0000000..83a4bfd
--- /dev/null
@@ -0,0 +1,63 @@
+package org.simantics.objmap.rules.range;\r
+\r
+import java.lang.reflect.Field;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * Accesses the given field of the element.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class FieldAccessor<T> implements IRangeAccessor<T> {\r
+    \r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+       Field field;\r
+\r
+       public FieldAccessor(Field field) {\r
+               this.field = field;\r
+       }\r
+\r
+       @Override\r
+       public T get(Object element) throws MappingException {\r
+               try {\r
+                   T result = (T)field.get(element);\r
+                   \r
+               if(LOGGER.isInfoEnabled())\r
+                   LOGGER.info("        FieldAccessor.get " +\r
+                           field.getName() + " -> " + result\r
+                   );\r
+               \r
+                       return result;\r
+               } catch (IllegalArgumentException e) {\r
+                       throw new MappingException(e);\r
+               } catch (IllegalAccessException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public boolean set(Object element, T value) throws MappingException {\r
+               try {\r
+                       Object currentValue = field.get(element);\r
+                       \r
+            if(LOGGER.isInfoEnabled())\r
+                LOGGER.info("        FieldAccessor.set " +\r
+                        field.getName() + " " + currentValue +  \r
+                        " -> " + value\r
+                );\r
+                       \r
+                       if(currentValue == null ? value == null : currentValue.equals(value))\r
+                               return false;\r
+                       field.set(element, value);\r
+                       return true;\r
+               } catch (IllegalArgumentException e) {\r
+                       throw new MappingException(e);\r
+               } catch (IllegalAccessException e) {\r
+                       throw new MappingException(e);\r
+               }\r
+       }       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/rules/range/IRangeAccessor.java b/org.simantics.objmap/src/org/simantics/objmap/rules/range/IRangeAccessor.java
new file mode 100644 (file)
index 0000000..d46d6b7
--- /dev/null
@@ -0,0 +1,12 @@
+package org.simantics.objmap.rules.range;\r
+\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * Provides access to some property of range elements.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IRangeAccessor<T> {\r
+       T get(Object element) throws MappingException;\r
+       boolean set(Object element, T value) throws MappingException;\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/schema/MappingSchemas.java b/org.simantics.objmap/src/org/simantics/objmap/schema/MappingSchemas.java
new file mode 100644 (file)
index 0000000..472915b
--- /dev/null
@@ -0,0 +1,83 @@
+package org.simantics.objmap.schema;\r
+\r
+import java.lang.annotation.Annotation;\r
+import java.lang.reflect.Field;\r
+import java.lang.reflect.Method;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.annotations.GraphType;\r
+import org.simantics.objmap.annotations.RelatedValue;\r
+import org.simantics.objmap.annotations.meta.HasClassRuleFactory;\r
+import org.simantics.objmap.annotations.meta.HasFieldRuleFactory;\r
+import org.simantics.objmap.annotations.meta.HasMethodRuleFactory;\r
+\r
+/**\r
+ * Static utility methods for mapping schemas\r
+ * @author Hannu Niemistö\r
+ */\r
+public class MappingSchemas {\r
+    \r
+    /**\r
+     * Creates a new SimpleLinkType based on the annotations in the given class.\r
+     * @throws IllegalAccessException \r
+     * @throws InstantiationException \r
+     * @see GraphType\r
+     * @see RelatedValue\r
+     */\r
+       public static SimpleLinkType fromAnnotations(ReadGraph g, Class<?> clazz) throws DatabaseException, InstantiationException, IllegalAccessException {\r
+           GraphType graphType = clazz.getAnnotation(GraphType.class);\r
+           \r
+           ArrayList<IMappingRule> rules = new ArrayList<IMappingRule>();\r
+           collectRulesFromAnnotations(g, clazz, rules);\r
+           \r
+           return new SimpleLinkType(\r
+                   g.getResourceByURI(graphType.value()), \r
+                clazz, rules);    \r
+       }\r
+       \r
+       public static void collectRulesFromAnnotations(ReadGraph g, Class<?> clazz, Collection<IMappingRule> rules) throws DatabaseException, InstantiationException, IllegalAccessException {\r
+           Class<?> superclass = clazz.getSuperclass();\r
+           if(superclass != null)\r
+               collectRulesFromAnnotations(g, superclass, rules);\r
+               \r
+        for(Annotation annotation : clazz.getAnnotations()) {\r
+            HasClassRuleFactory factory = \r
+                annotation.annotationType().getAnnotation(HasClassRuleFactory.class);\r
+            if(factory != null) {\r
+                rules.add(factory.value().newInstance()\r
+                        .create(g, annotation, clazz));\r
+            }\r
+        }\r
+\r
+        for(Field f : clazz.getDeclaredFields()) {\r
+            f.setAccessible(true);\r
+\r
+            for(Annotation annotation : f.getAnnotations()) {\r
+                HasFieldRuleFactory factory = \r
+                    annotation.annotationType().getAnnotation(HasFieldRuleFactory.class);\r
+                if(factory != null) {\r
+                    rules.add(factory.value().newInstance()\r
+                            .create(g, annotation, f));\r
+                }\r
+            }\r
+        }\r
+\r
+        for(Method m : clazz.getDeclaredMethods()) {\r
+            m.setAccessible(true);\r
+\r
+            for(Annotation annotation : m.getAnnotations()) {\r
+                HasMethodRuleFactory factory = \r
+                    annotation.annotationType().getAnnotation(HasMethodRuleFactory.class);\r
+                if(factory != null) {\r
+                    rules.add(factory.value().newInstance()\r
+                            .create(g, annotation, m));\r
+                }\r
+            }\r
+        }\r
+    }\r
+       \r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/schema/SimpleLinkType.java b/org.simantics.objmap/src/org/simantics/objmap/schema/SimpleLinkType.java
new file mode 100644 (file)
index 0000000..1506834
--- /dev/null
@@ -0,0 +1,121 @@
+package org.simantics.objmap.schema;\r
+\r
+import java.util.ArrayList;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.objmap.IFunction;\r
+import org.simantics.objmap.ILinkType;\r
+import org.simantics.objmap.IMappingRule;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * A link type that is associated with single domain and range type (class).\r
+ * SimpleLinkType is composed of simpler rules whose combination determines\r
+ * its update policy.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class SimpleLinkType implements ILinkType {\r
+    \r
+    static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+    \r
+    Resource domainType;\r
+    Class<?> rangeType;\r
+    ArrayList<IMappingRule> rules;\r
+    \r
+    public SimpleLinkType(Resource domainType, Class<?> rangeType,\r
+            ArrayList<IMappingRule> rules) {\r
+        this.domainType = domainType;\r
+        this.rangeType = rangeType;\r
+        this.rules = rules;\r
+    }\r
+\r
+    public SimpleLinkType(Resource domainType, Class<?> rangeType) {\r
+        this(domainType, rangeType, new ArrayList<IMappingRule>());\r
+    }\r
+\r
+    /**\r
+     * Adds a new rule to this link type that is enforced\r
+     * during updates.\r
+     */\r
+    public void addRule(IMappingRule rule) {\r
+        rules.add(rule);\r
+    }\r
+    \r
+    @Override\r
+    public Resource createDomainElement(WriteGraph g, Object rangeElement)\r
+            throws MappingException {\r
+        try {\r
+            if(LOGGER.isInfoEnabled())\r
+                LOGGER.info("SimpleLinkType.createDomainElement " +\r
+                        rangeElement.toString()\r
+                );\r
+            Resource result = g.newResource();\r
+            g.claim(result, g.getBuiltins().InstanceOf, domainType);\r
+            return result;\r
+        } catch(DatabaseException e) {\r
+            throw new MappingException(e);\r
+        }\r
+    }\r
+    @Override\r
+    public Object createRangeElement(ReadGraph g, Resource domainElement)\r
+            throws MappingException {\r
+        try {\r
+            if(LOGGER.isInfoEnabled())\r
+                try { \r
+                    LOGGER.info("SimpleLinkType.createRangeElement " +\r
+                            GraphUtils.getReadableName(g, domainElement)\r
+                            );\r
+                } catch(DatabaseException e) {\r
+                    throw new MappingException(e);\r
+                }\r
+            return rangeType.newInstance();\r
+        } catch (InstantiationException e) {\r
+            throw new MappingException(e);\r
+        } catch (IllegalAccessException e) {\r
+            throw new MappingException(e);\r
+        }\r
+    }\r
+    @Override\r
+    public boolean updateDomain(WriteGraph g, IFunction<Object, Resource> map,\r
+            Resource domainElement, Object rangeElement)\r
+            throws MappingException {\r
+        if(LOGGER.isInfoEnabled())\r
+            try { \r
+                LOGGER.info("SimpleLinkType.updateDomain " +\r
+                        GraphUtils.getReadableName(g, domainElement) + " " +\r
+                        rangeElement.toString()\r
+                        );\r
+            } catch(DatabaseException e) {\r
+                throw new MappingException(e);\r
+            }\r
+        \r
+        boolean updated = false;\r
+        for(IMappingRule rule : rules)\r
+               updated |= rule.updateDomain(g, map, domainElement, rangeElement);\r
+        return updated;\r
+    }\r
+    @Override\r
+    public boolean updateRange(ReadGraph g, IFunction<Resource, Object> map,\r
+            Resource domainElement, Object rangeElement)\r
+            throws MappingException {\r
+        if(LOGGER.isInfoEnabled())\r
+            try { \r
+                LOGGER.info("SimpleLinkType.updateRange " +\r
+                        GraphUtils.getReadableName(g, domainElement) + " " +\r
+                        rangeElement.toString()\r
+                        );\r
+            } catch(DatabaseException e) {\r
+                throw new MappingException(e);\r
+            }\r
+        \r
+        boolean updated = false;\r
+        for(IMappingRule rule : rules)\r
+            updated |= rule.updateRange(g, map, domainElement, rangeElement);\r
+        return updated;\r
+    }\r
+}\r
diff --git a/org.simantics.objmap/src/org/simantics/objmap/schema/SimpleSchema.java b/org.simantics.objmap/src/org/simantics/objmap/schema/SimpleSchema.java
new file mode 100644 (file)
index 0000000..5128016
--- /dev/null
@@ -0,0 +1,55 @@
+package org.simantics.objmap.schema;\r
+\r
+import gnu.trove.THashMap;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.objmap.ILinkType;\r
+import org.simantics.objmap.IMappingSchema;\r
+import org.simantics.objmap.MappingException;\r
+\r
+/**\r
+ * An implementation of IMappingSchema that contains\r
+ * only SimpleLinkTypes. The link type of any domain\r
+ * element is based solely on its type in database and\r
+ * the link type of any range element is based on its class.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class SimpleSchema implements IMappingSchema {\r
+\r
+    THashMap<Resource, SimpleLinkType> domainLinkTypes = \r
+        new THashMap<Resource, SimpleLinkType>();\r
+    THashMap<Class<?>, SimpleLinkType> rangeLinkTypes = \r
+        new THashMap<Class<?>, SimpleLinkType>();\r
+    \r
+    public void addLinkType(SimpleLinkType linkType) {\r
+        domainLinkTypes.put(linkType.domainType, linkType);\r
+        rangeLinkTypes.put(linkType.rangeType, linkType);\r
+    }\r
+    \r
+    @Override\r
+    public ILinkType linkTypeOfDomainElement(ReadGraph g, Resource element) throws MappingException {        \r
+        try {\r
+            ILinkType type = domainLinkTypes.get(\r
+                    g.getSingleObject(element, g.getBuiltins().InstanceOf));\r
+            if(type == null) \r
+                               throw new MappingException("Didn't find a link type for " +\r
+                                               GraphUtils.getReadableName(g, element) + "."\r
+                                               );\r
+               return type;\r
+        } catch (DatabaseException e) {\r
+            throw new MappingException(e);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public ILinkType linkTypeOfRangeElement(Object element) throws MappingException {\r
+       ILinkType type = rangeLinkTypes.get(element.getClass());\r
+               if(type == null) \r
+                       throw new MappingException("Didn't find a link type for " +     element + ".");\r
+               return type;\r
+    }\r
+\r
+}\r