You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by fa...@apache.org on 2009/03/05 18:30:25 UTC

svn commit: r750517 [1/4] - in /openjpa/trunk: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/ openjpa-kernel/src/...

Author: fancy
Date: Thu Mar  5 17:30:20 2009
New Revision: 750517

URL: http://svn.apache.org/viewvc?rev=750517&view=rev
Log:
OPENJPA-879 JPA2 Query support for selecting KEY, ENTRY and VALUE of a Map value

Added:
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapEntry.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapKey.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Division.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/TestMany2ManyMap.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Department.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Division.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/TestMany2ManyMapEx1.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/EmployeePK.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhonePK.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/TestMany2ManyMapEx10.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex2/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex2/Department.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex2/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex2/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex2/TestMany2ManyMapEx2.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex3/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex3/Department.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex3/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex3/FullName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex3/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex3/TestMany2ManyMapEx3.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex4/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex4/Division.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex4/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex4/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex4/TestMany2ManyMapEx4.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex5/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex5/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex5/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex5/TestMany2ManyMapEx5.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex6/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex6/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex6/FullName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex6/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex6/TestMany2ManyMapEx6.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex7/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex7/Division.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex7/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex7/FullName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex7/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex7/TestMany2ManyMapEx7.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex8/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex8/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex8/FullName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex8/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex8/TestMany2ManyMapEx8.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex9/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex9/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex9/FullName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex9/FullPhoneName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex9/PhoneNumber.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex9/TestMany2ManyMapEx9.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/Department1.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/Department2.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/Department3.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/Employee1.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/Employee2.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/Employee3.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/EmployeeName3.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/EmployeePK2.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex0/TestSpec10_1_26.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex1/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex1/Department.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex1/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex1/TestSpec10_1_26_Ex1.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex2/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex2/Department.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex2/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex2/EmployeePK.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex2/TestSpec10_1_26_Ex2.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex3/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex3/Department.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex3/Employee.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex3/EmployeeName.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/spec_10_1_26_ex3/TestSpec10_1_26_Ex3.java
Modified:
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
    openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java Thu Mar  5 17:30:20 2009
@@ -408,6 +408,14 @@
         return new Type((Val) val);
     }
 
+    public Value mapEntry(Value key, Value val) {
+        return new MapEntry((Val) key, (Val) val);
+    }
+
+    public Value mapKey(Value key, Value val) {
+        return new MapKey((Val) key, (Val) val);
+    }
+
     public Value getObjectId(Value val) {
         if (val instanceof Const)
             return new ConstGetObjectId((Const) val);

Added: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapEntry.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapEntry.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapEntry.java (added)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapEntry.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.jdbc.kernel.exps;
+
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.meta.ClassMetaData;
+
+/**
+ * Returns the Map.Entry<K,V> of a map value.
+ *
+ * @author Catalina Wei
+ * @since 2.0.0
+ */
+public class MapEntry
+    extends AbstractVal {
+
+    private final Val _key;
+    private final Val _val;
+    private ClassMetaData _meta = null;
+    private Class _cast = null;
+    private Class _type = null;
+
+    /**
+     * Constructor. Provide the map value to operate on.
+     */
+    public MapEntry(Val key, Val val) {
+        ((PCPath) key).getKey();
+        _key = key;
+        _val = val;
+    }
+
+    /**
+     * Expression state.
+     */
+    public static class EntryExpState
+        extends ExpState {
+        public ExpState key;
+        public ExpState val;
+    
+        EntryExpState(ExpState key, ExpState val) {
+            this.key = key;
+            this.val = val;
+        }
+    }
+
+    public void appendTo(Select sel, ExpContext ctx, ExpState state,
+        SQLBuffer sql, int index) {
+    }
+
+    public void calculateValue(Select sel, ExpContext ctx, ExpState state,
+        Val other, ExpState otherState) {
+        _val.calculateValue(sel, ctx, state, other, otherState);
+        _key.calculateValue(sel, ctx, state, other, otherState);
+    }
+
+    public void groupBy(Select sel, ExpContext ctx, ExpState state) {
+    }
+
+    public ExpState initialize(Select sel, ExpContext ctx, int flags) {
+        ExpState val = _val.initialize(sel, ctx, flags);
+        ExpState key = _key.initialize(sel, ctx, flags);
+        return new EntryExpState(key, val);
+    }
+
+    public int length(Select sel, ExpContext ctx, ExpState state) {
+        return 1;
+    }
+
+    public Object load(ExpContext ctx, ExpState state, Result res)
+        throws SQLException {
+        EntryExpState estate = (EntryExpState) state;
+        Object key = _key.load(ctx, estate.key, res);
+        Object val = _val.load(ctx, estate.val, res);
+
+        return new Entry(key, val);
+    }
+
+    public void orderBy(Select sel, ExpContext ctx, ExpState state, boolean asc) {
+    }
+
+    public void select(Select sel, ExpContext ctx, ExpState state, boolean pks) {
+        EntryExpState estate = (EntryExpState) state;
+        _key.selectColumns(sel, ctx, estate.key, pks);
+        _val.selectColumns(sel, ctx, estate.val, pks);
+    }
+
+    public void selectColumns(Select sel, ExpContext ctx, ExpState state,
+        boolean pks) {
+    }
+
+    public ClassMetaData getMetaData() {
+        return _meta;
+    }
+
+    public Class getType() {
+        return Map.Entry.class;
+    }
+
+    public void setImplicitType(Class type) {
+    }
+
+    public void setMetaData(ClassMetaData meta) {
+        _meta = meta;        
+    }
+
+    private class Entry<K,V> implements Map.Entry<K, V> {
+        private final K key;
+        private final V value;
+
+        public Entry(K k, V v) {
+            key = k;
+            value = v;
+        }
+        public K getKey() {
+            return key;
+        }
+
+        public V getValue() {
+            return value;
+        }
+
+        public V setValue(V v) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean equals(Object other) {
+            if (other instanceof Map.Entry == false)
+                return false;
+            Map.Entry that = (Map.Entry)other;
+            return (this.key == null ?
+                that.getKey() == null : key.equals(that.getKey())) &&
+                (value == null ?
+                that.getValue() == null : value.equals(that.getValue()));
+        }
+
+        public int hashCode() {
+            return  (key == null   ? 0 : key.hashCode()) ^
+            (value == null ? 0 : value.hashCode());
+        }
+    }
+}

Added: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapKey.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapKey.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapKey.java (added)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/MapKey.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.jdbc.kernel.exps;
+
+import java.sql.SQLException;
+
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.meta.ClassMetaData;
+
+/**
+ * Returns the key of a map value.
+ *
+ * @author Catalina Wei
+ * @since 2.0.0
+ */
+public class MapKey
+    extends AbstractVal {
+
+    private final Val _key;
+    private final Val _val;
+    private ClassMetaData _meta = null;
+    private Class _cast = null;
+    private Class _type = null;
+
+    /**
+     * Constructor. Provide the map value to operate on.
+     */
+    public MapKey(Val key, Val val) {
+        ((PCPath) key).getKey();
+        _key = key;
+        _val = val;
+    }
+
+    /**
+     * Expression state.
+     */
+    public static class KeyExpState
+        extends ExpState {
+        public ExpState key;
+        public ExpState val;
+    
+        KeyExpState(ExpState key, ExpState val) {
+            this.key = key;
+            this.val = val;
+        }
+    }
+
+    public void appendTo(Select sel, ExpContext ctx, ExpState state,
+        SQLBuffer sql, int index) {
+    }
+
+    public void calculateValue(Select sel, ExpContext ctx, ExpState state,
+        Val other, ExpState otherState) {
+        _val.calculateValue(sel, ctx, state, other, otherState);
+        _key.calculateValue(sel, ctx, state, other, otherState);
+    }
+
+    public void groupBy(Select sel, ExpContext ctx, ExpState state) {
+    }
+
+    public ExpState initialize(Select sel, ExpContext ctx, int flags) {
+        ExpState val = _val.initialize(sel, ctx, flags);
+        ExpState key = _key.initialize(sel, ctx, flags);
+        return new KeyExpState(key, val);
+    }
+
+    public int length(Select sel, ExpContext ctx, ExpState state) {
+        return 1;
+    }
+
+    public Object load(ExpContext ctx, ExpState state, Result res)
+        throws SQLException {
+        KeyExpState estate = (KeyExpState) state;
+        Object key = _key.load(ctx, estate.key, res);
+        return key;
+    }
+
+    public void orderBy(Select sel, ExpContext ctx, ExpState state, boolean asc) {
+    }
+
+    public void select(Select sel, ExpContext ctx, ExpState state, boolean pks) {
+        KeyExpState estate = (KeyExpState) state;
+        _key.selectColumns(sel, ctx, estate.key, pks);
+    }
+
+    public void selectColumns(Select sel, ExpContext ctx, ExpState state,
+        boolean pks) {
+    }
+
+    public ClassMetaData getMetaData() {
+        return _meta;
+    }
+
+    public Class getType() {
+        return _key.getType();
+    }
+
+    public void setImplicitType(Class type) {
+    }
+
+    public void setMetaData(ClassMetaData meta) {
+        _meta = meta;        
+    }
+}

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java Thu Mar  5 17:30:20 2009
@@ -25,11 +25,14 @@
 import java.util.ListIterator;
 
 import org.apache.commons.lang.ObjectUtils;
+import org.apache.openjpa.enhance.PersistenceCapable;
 import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
 import org.apache.openjpa.jdbc.meta.ClassMapping;
 import org.apache.openjpa.jdbc.meta.Discriminator;
 import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
 import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.meta.strats.HandlerRelationMapTableFieldStrategy;
 import org.apache.openjpa.jdbc.schema.Column;
 import org.apache.openjpa.jdbc.schema.ForeignKey;
 import org.apache.openjpa.jdbc.schema.Schemas;
@@ -40,6 +43,7 @@
 import org.apache.openjpa.kernel.Broker;
 import org.apache.openjpa.kernel.Filters;
 import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.kernel.StateManagerImpl;
 import org.apache.openjpa.kernel.StoreContext;
 import org.apache.openjpa.kernel.exps.CandidatePath;
 import org.apache.openjpa.lib.util.Localizer;
@@ -240,6 +244,8 @@
         if (pstate.field == null)
             return _class;
         if (_key) {
+            if (pstate.field.getKey().getValueMappedBy() != null)
+                return _class;
             if (pstate.field.getKey().getTypeCode() == JavaTypes.PC)
                 return pstate.field.getKeyMapping().getTypeMapping();
             return null;
@@ -363,8 +369,8 @@
 
     public FieldMetaData last() {
         Action act = lastFieldAction();
-        return (act == null) ? null : isXPath() ? _xmlfield :
-            (FieldMetaData) act.data;
+        return (act == null || act.op == Action.GET_KEY) ? null : isXPath() ?
+            _xmlfield : (FieldMetaData) act.data;
     }
 
     /**
@@ -395,7 +401,8 @@
         if (act != null && act.op == Action.GET_XPATH)
             return ((XMLMetaData) act.data).getType();
         
-        FieldMetaData fld = (act == null) ? null : (FieldMetaData) act.data;
+        FieldMetaData fld = (act == null || act.op == Action.GET_KEY) ? null : 
+            (FieldMetaData) act.data;
         boolean key = act != null && act.op == Action.GET_KEY;
         if (fld != null) {
             switch (fld.getDeclaredTypeCode()) {
@@ -437,7 +444,7 @@
         Action action;
         Variable var;
         Iterator itr = (_actions == null) ? null : _actions.iterator();
-        FieldMapping field;
+        FieldMapping field = null;
         while (itr != null && itr.hasNext()) {
             action = (Action) itr.next();
 
@@ -459,7 +466,13 @@
             } else {
                 // move past the previous field, if any
                 field = (action.op == Action.GET_XPATH) ? (FieldMapping) _xmlfield :
-                    (FieldMapping) action.data;
+                    (action.op == Action.GET_KEY) ? field : (FieldMapping) action.data;
+
+                // mark if the next traversal should go through
+                // the key rather than value
+                key = action.op == Action.GET_KEY;
+                forceOuter |= action.op == Action.GET_OUTER;
+
                 if (pstate.field != null) {
                     // if this is the second-to-last field and the last is
                     // the related field this field joins to, no need to
@@ -472,11 +485,6 @@
                     rel = traverseField(pstate, key, forceOuter, false);
                 }
 
-                // mark if the next traversal should go through
-                // the key rather than value
-                key = action.op == Action.GET_KEY;
-                forceOuter |= action.op == Action.GET_OUTER;
-
                 // get mapping for the current field
                 pstate.field = field;
                 owner = pstate.field.getDefiningMapping();
@@ -515,6 +523,11 @@
         if (_varName != null)
             pstate.joins = pstate.joins.setVariable(_varName);
 
+        // if last action is key action, avoid redundant joins
+        if (key) {
+            return pstate;
+        }
+
         // if we're not comparing to null or doing an isEmpty, then
         // join into the data on the final field; obviously we can't do these
         // joins when comparing to null b/c the whole purpose is to see
@@ -740,13 +753,29 @@
             if (pks)
                 return mapping.getObjectId(ctx.store, res, null, true, 
                     pstate.joins);
+            if (_key) {
+                if (pstate.field.getKey().getValueMappedBy() != null) {
+                    Object obj =  res.load(mapping, ctx.store, ctx.fetch,
+                        pstate.joins);
+                    StateManagerImpl sm = (StateManagerImpl) 
+                        ((PersistenceCapable) obj).pcGetStateManager();
+                    obj = sm.fetch(_class.getField(pstate.field.getKey().
+                        getValueMappedBy()).getIndex());
+                    return obj;
+                }
+                else if (pstate.field.getKey().isEmbedded())
+                    return loadEmbeddedMapKey(ctx, state, res);
+            }
+
             return res.load(mapping, ctx.store, ctx.fetch, pstate.joins);
         }
 
         Object ret;
         if (_key)
-            ret = pstate.field.loadKeyProjection(ctx.store, ctx.fetch, res, 
-                pstate.joins);
+            // Map key is a java primitive type
+            //    example: Map<Integer, Employee> emps
+            ret = res.getObject(pstate.cols[0],
+                JavaSQLTypes.JDBC_DEFAULT, pstate.joins);
         else
             ret = pstate.field.loadProjection(ctx.store, ctx.fetch, res, 
                 pstate.joins);
@@ -755,6 +784,23 @@
         return ret;
     }
 
+    private Object loadEmbeddedMapKey(ExpContext ctx, ExpState state,
+        Result res) throws SQLException {
+        PathExpState pstate = (PathExpState) state;
+        // consume keyProjection
+        PersistenceCapable pc = (PersistenceCapable) res.load(_candidate,
+            ctx.store, ctx.fetch, pstate.joins);
+        if (pstate.field.getStrategy() == null ||
+            !(pstate.field.getStrategy() instanceof
+                HandlerRelationMapTableFieldStrategy))
+            throw new RuntimeException("Invalid map field strategy");
+        HandlerRelationMapTableFieldStrategy strategy =
+            (HandlerRelationMapTableFieldStrategy) pstate.field.getStrategy();
+        return strategy
+            .loadKey((OpenJPAStateManager) pc.pcGetStateManager(),
+                ctx.store, ctx.fetch, res, pstate.joins);
+    }
+
     public void calculateValue(Select sel, ExpContext ctx, ExpState state, 
         Val other, ExpState otherState) {
         // we don't create the SQL b/c it forces the Select to cache aliases

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Type.java Thu Mar  5 17:30:20 2009
@@ -40,7 +40,8 @@
     public Type(Val val) {
         super(val);
         setMetaData(val.getMetaData());
-        _disc = ((ClassMapping) getMetaData()).getDiscriminator();
+        if (getMetaData() != null)
+            _disc = ((ClassMapping) getMetaData()).getDiscriminator();
     }
 
     public ExpState initialize(Select sel, ExpContext ctx, int flags) {

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java Thu Mar  5 17:30:20 2009
@@ -416,6 +416,18 @@
     public Value type(Value target);
 
     /**
+     * Return the map entry of the given value.
+     *
+     * @since 2.0.0
+     */
+    public Value mapEntry(Value key, Value val);
+
+    /**
+     * Return the map key of the given value
+     */
+    public Value mapKey(Value key, Value val);
+
+    /**
      * Return distinct values of the given value. This is typically used
      * within aggregates, for example: max(distinct(path))
 	 *

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java Thu Mar  5 17:30:20 2009
@@ -35,6 +35,7 @@
 import org.apache.openjpa.lib.util.Localizer;
 import org.apache.openjpa.meta.ClassMetaData;
 import org.apache.openjpa.util.ImplHelper;
+import org.apache.openjpa.util.UnsupportedException;
 import org.apache.openjpa.util.UserException;
 
 /**
@@ -647,6 +648,14 @@
         return new Type((Val) val);
     }
 
+    public Value mapEntry(Value key, Value val) {
+        throw new UnsupportedException("not implemented yet");
+    }
+
+    public Value mapKey(Value key, Value val) {
+        throw new UnsupportedException("not implemented yet");
+    }
+
     public Value getObjectId(Value val) {
         return new GetObjectId((Val) val);
     }

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java Thu Mar  5 17:30:20 2009
@@ -49,6 +49,7 @@
 import org.apache.openjpa.lib.log.Log;
 import org.apache.openjpa.meta.ClassMetaData;
 import org.apache.openjpa.meta.FieldMetaData;
+import org.apache.openjpa.meta.JavaTypes;
 import org.apache.openjpa.meta.MetaDataRepository;
 import org.apache.openjpa.meta.ValueMetaData;
 import org.apache.openjpa.util.InternalException;
@@ -958,6 +959,17 @@
             case JJTIDENTIFICATIONVARIABLE:
                 return getIdentifier(node);
 
+            case JJTQUALIFIEDPATH:
+                // TODO: to be implemented.
+
+            case JJTQUALIFIEDIDENTIFIER:
+                // KEY(e), VALUE(e), ENTRY(e)
+                return getQualifiedIdentifier(onlyChild(node));
+
+            case JJTGENERALIDENTIFIER:
+                // KEY(e), VALUE(e)
+                return getQualifiedIdentifier(onlyChild(node));
+
             case JJTNOT:
                 return factory.not(getExpression(onlyChild(node)));
 
@@ -1412,6 +1424,35 @@
             new Object[]{ name }, null);
     }
 
+    private Value getQualifiedIdentifier(JPQLNode node) {
+        JPQLNode id = onlyChild(node);               
+        Path path = (Path) getValue(id);
+        
+        FieldMetaData fld = path.last();
+        
+        if (fld != null) {            
+            // validate the field is of type java.util.Map
+            if (fld.getDeclaredTypeCode() != JavaTypes.MAP) {
+                String oper = "VALUE";
+                if (node.id == JJTENTRY)
+                    oper = "ENTRY";        
+                else if (node.id == JJTKEY)
+                    oper = "KEY";
+                throw parseException(EX_USER, "bad-qualified-identifier",
+                    new Object[]{ id.text, oper}, null);
+            }
+        }        
+
+        if (node.id == JJTVALUE)
+            return path;
+
+        Value value = getValue(id);
+        if (node.id == JJTKEY)
+            return factory.mapKey(path, value);
+        else            
+            return factory.mapEntry(path, value);
+    }
+
     private Value getTypeLiteral(JPQLNode node) {
         JPQLNode type = onlyChild(node);
         final String name = type.text;
@@ -1460,7 +1501,7 @@
     /**
      * Process type_discriminator
      *     type_discriminator ::=
-     *         TYPE(identification_variable |
+     *         TYPE(general_identification_variable |
      *         single_valued_object_path_expression |
      *         input_parameter )
      */
@@ -1475,6 +1516,9 @@
         case JJTPOSITIONALINPUTPARAMETER:
             return factory.type(getParameter(node.text, true, false));
 
+        case JJTGENERALIDENTIFIER:
+            return factory.type(getQualifiedIdentifier(onlyChild(node)));
+
         default:
             // TODO: enforce jpa2.0 spec rules.
             // A single_valued_object_field is designated by the name of

Modified: openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties?rev=750517&r1=750516&r2=750517&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties (original)
+++ openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties Thu Mar  5 17:30:20 2009
@@ -73,3 +73,5 @@
 jpql-parse-error: "{1}" while parsing JPQL "{0}". See nested stack trace for \
 	original parse error.
 not-type-literal: The specified node ("{0}") is not a valid entity type literal.
+bad-qualified-identifier: The identifier "{0}" in "{1}" operator is not \
+    referring to an association field of type java.util.Map.

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Division.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Division.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Division.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Division.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex0;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name="m2mDivision")
+public class Division {
+    @Id
+    int id;
+
+    String name;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean equals(Object o) {
+        Division d = (Division) o;
+        if (d.name.equals(name) &&
+            d.getId() == id)
+            return true;
+        return false;
+    }
+
+    public int hashCode() {
+        int ret = 0;
+        ret = ret * 31 + name.hashCode();
+        ret = ret *31 + id;
+        return ret;
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Employee.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Employee.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Employee.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/Employee.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex0;
+
+import javax.persistence.*;
+
+import java.util.*;
+
+@Entity
+@Table(name="m2mEmp")
+public class Employee {
+    @Id
+    int empId;
+
+    @ManyToMany // Bidirectional
+    Map<Division, PhoneNumber> phones =
+        new HashMap<Division, PhoneNumber>();
+
+    public Map<Division, PhoneNumber> getPhoneNumbers() {
+        return phones;
+    }
+
+    public void addPhoneNumber(Division d, PhoneNumber phoneNumber) {
+        phones.put(d, phoneNumber);
+    }
+
+    public void removePhoneNumber(Division d) {
+        phones.remove(d);
+    }
+
+    public int getEmpId() {
+        return empId;
+    }
+
+    public void setEmpId(int empId) {
+        this.empId = empId;
+    }
+
+    public boolean equals(Object o) {
+        Employee e = (Employee) o;
+        Map<Division, PhoneNumber> map = e.getPhoneNumbers();
+        if (map.size() != phones.size())
+            return false;
+        Collection<Map.Entry<Division, PhoneNumber>> entries =
+            (Collection<Map.Entry<Division, PhoneNumber>>) phones.entrySet();
+        for (Map.Entry<Division, PhoneNumber> entry : entries) {
+            Division key = entry.getKey();
+            PhoneNumber p = entry.getValue();
+            PhoneNumber p0 = map.get(key);
+            if (p.getNumber() != p0.getNumber())
+                return false;
+        }
+        return true;
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/PhoneNumber.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/PhoneNumber.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/PhoneNumber.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/PhoneNumber.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex0;
+
+import javax.persistence.*;
+
+import java.util.*;
+
+@Entity
+@Table(name="m2mPhone")
+public class PhoneNumber {
+    @Id
+    int number;
+
+    @ManyToMany(mappedBy = "phones")
+    Map<Division, Employee> emps = new HashMap<Division, Employee>();
+
+    public int getNumber() {
+        return number;
+    }
+
+    public void setNumber(int number) {
+        this.number = number;
+    }
+
+    public Map<Division, Employee> getEmployees() {
+        return emps;
+    }
+
+    public void addEmployees(Division d, Employee employee) {
+        emps.put(d, employee);
+    }
+
+    public void removeEmployee(Division d) {
+        emps.remove(d);
+    }
+
+    public boolean equals(Object o) {
+        PhoneNumber p = (PhoneNumber) o;
+        Map<Division, Employee> map = p.getEmployees();
+        if (map.size() != emps.size())
+            return false;
+        Collection<Map.Entry<Division, Employee>> entries =
+            (Collection<Map.Entry<Division, Employee>>) emps.entrySet();
+        for (Map.Entry<Division, Employee> entry : entries) {
+            Division key = entry.getKey();
+            Employee e = entry.getValue(); 
+            Employee e0 = map.get(key);
+            if (e.getEmpId() != e0.getEmpId())
+                return false;
+        }
+        return true;
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/TestMany2ManyMap.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/TestMany2ManyMap.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/TestMany2ManyMap.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex0/TestMany2ManyMap.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex0;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Query;
+
+import junit.framework.Assert;
+
+import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
+import org.apache.openjpa.lib.jdbc.JDBCEvent;
+import org.apache.openjpa.lib.jdbc.JDBCListener;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+
+public class TestMany2ManyMap extends SingleEMFTestCase {
+
+    public int numEmployees = 2;
+    public int numPhoneNumbersPerEmployee = 2;
+    public Map<Integer, Employee> empMap = new HashMap<Integer, Employee>();
+    public Map<Integer, PhoneNumber> phoneMap =
+        new HashMap<Integer, PhoneNumber>();
+
+    public List<String> namedQueries = new ArrayList<String>();
+
+    public int empId = 1;
+    public int phoneId = 1;
+    public int divId = 1;
+
+    protected List<String> sql = new ArrayList<String>();
+    protected int sqlCount;
+
+    public void setUp() {
+        super.setUp(CLEAR_TABLES, Division.class,
+            Employee.class, PhoneNumber.class,
+            "openjpa.jdbc.JDBCListeners", 
+            new JDBCListener[] { this.new Listener() });
+        createObj();
+    }
+
+    public void testQueryQualifiedId() throws Exception {
+        EntityManager em = emf.createEntityManager();
+
+        String query = "select KEY(e) from PhoneNumber p, " +
+            " in (p.emps) e order by e.empId";
+        List rs = em.createQuery(query).getResultList();
+        Division d = (Division) rs.get(0);
+
+        String query2 = "select KEY(p) from Employee e, " +
+            " in (e.phones) p";
+        List rs2 = em.createQuery(query2).getResultList();
+        Division d2 = (Division) rs2.get(0);
+
+        String query3 = "select VALUE(e) from PhoneNumber p, " +
+            " in (p.emps) e";
+
+        List rs3 = em.createQuery(query3).getResultList();
+        Employee e = (Employee) rs3.get(0);
+
+        em.clear();
+        String query4 = "select ENTRY(e) from PhoneNumber p, " +
+            " in (p.emps) e order by e.empId";
+        List rs4 = em.createQuery(query4).getResultList();
+        Map.Entry me = (Map.Entry) rs4.get(0);
+
+        assertTrue(d.equals(me.getKey()));
+
+        String query5 = "select TYPE(KEY(p)) from Employee e, " +
+            " in (e.phones) p";
+        List rs5 = em.createQuery(query5).getResultList();
+        assertEquals(Division.class, rs5.get(0));
+
+        em.close();
+    }
+
+    public void testQueryObject() throws Exception {
+        queryObj();
+        findObj();
+    }
+
+    public List<String> getSql() {
+        return sql;
+    }
+
+    public int getSqlCount() {
+        return sqlCount;
+    }
+
+    public void createObj() {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        for (int i = 0; i < numEmployees; i++) {
+            Employee e = createEmployee(em, empId++);
+            empMap.put(e.getEmpId(), e);
+        }
+        tran.begin();
+        em.flush();
+        tran.commit();
+        em.close();
+    }
+
+    public Employee createEmployee(EntityManager em, int id) {
+        Employee e = new Employee();
+        e.setEmpId(id);
+        for (int i = 0; i < numPhoneNumbersPerEmployee; i++) { 
+            PhoneNumber phoneNumber = new PhoneNumber();
+            phoneNumber.setNumber(phoneId++);
+            Division d1 = createDivision(em, divId++);
+            Division d2 = createDivision(em, divId++);
+            phoneNumber.addEmployees(d1, e);
+            e.addPhoneNumber(d2, phoneNumber);
+            em.persist(phoneNumber);
+            phoneMap.put(phoneNumber.getNumber(), phoneNumber);
+            em.persist(d1);
+            em.persist(d2);
+        }
+        em.persist(e);
+        return e;
+    }
+
+    public Division createDivision(EntityManager em, int id) {
+        Division d = new Division();
+        d.setId(id);
+        d.setName("d" + id);
+        return d;
+    }
+
+    public void removeAll() {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        tran.begin();
+        Query q = em.createNativeQuery("delete from department");
+        q.executeUpdate();
+        q = em.createNativeQuery("delete from employee");
+        q.executeUpdate();
+        System.out.println("committing removes");
+        tran.commit();
+        em.close();
+    }
+
+    public void findObj() throws Exception {
+        EntityManager em = emf.createEntityManager();
+        Employee e = em.find(Employee.class, 1);
+        assertEmployee(e);
+
+        PhoneNumber p = em.find(PhoneNumber.class, 1);
+        assertPhoneNumber(p);
+        em.close();
+    }
+
+    public void queryObj() throws Exception {
+        queryEmployee();
+        queryPhoneNumber();
+    }
+
+    public void queryPhoneNumber() throws Exception {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        tran.begin();
+        Query q = em.createQuery("select p from PhoneNumber p");
+        List<PhoneNumber> ps = q.getResultList();
+        for (PhoneNumber p : ps) {
+            assertPhoneNumber(p);
+        }
+        tran.commit();
+        em.close();
+    }
+
+    public void queryEmployee() throws Exception {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        tran.begin();
+        Query q = em.createQuery("select e from Employee e");
+        List<Employee> es = q.getResultList();
+        for (Employee e : es) {
+            assertEmployee(e);
+        }
+        tran.commit();
+        em.close();
+    }
+
+    public void assertEmployee(Employee e) throws Exception {
+        int id = e.getEmpId();
+        Employee e0 = empMap.get(id);
+        Map<Division, PhoneNumber> phones0 = e0.getPhoneNumbers();
+        Map<Division, PhoneNumber> phones = e.getPhoneNumbers();
+        Assert.assertEquals(phones0.size(), phones.size());
+        checkPhoneMap(phones0, phones);
+    }
+
+    public void assertPhoneNumber(PhoneNumber p) throws Exception {
+        int number = p.getNumber();
+        PhoneNumber p0 = phoneMap.get(number);
+        Map<Division, Employee> es0 = p0.getEmployees();
+        Map<Division, Employee> es = p.getEmployees();
+        Assert.assertEquals(es0.size(), es.size());
+        checkEmpMap(es0, es);
+    }
+
+    public void checkPhoneMap(Map<Division, PhoneNumber> es0, 
+        Map<Division, PhoneNumber> es) throws Exception {
+        Collection<Map.Entry<Division, PhoneNumber>> entrySets0 =
+            es0.entrySet();
+        for (Map.Entry<Division, PhoneNumber> entry0 : entrySets0) {
+            Division d0 = entry0.getKey();
+            PhoneNumber p0 = entry0.getValue();
+            PhoneNumber p = es.get(d0);
+            if (!p0.equals(p))
+                throw new Exception("Assertion failure");
+        }
+    }
+
+    public void checkEmpMap(Map<Division, Employee> es0,
+        Map<Division, Employee> es) throws Exception {
+        Collection<Map.Entry<Division, Employee>> entrySets0 =
+            es0.entrySet();
+        for (Map.Entry<Division, Employee> entry0 : entrySets0) {
+            Division d0 = entry0.getKey();
+            Employee e0 = entry0.getValue();
+            Employee e = es.get(d0);
+            if (!e0.equals(e))
+                throw new Exception("Assertion failure");
+        }
+    }    
+
+    public class Listener extends AbstractJDBCListener {
+        @Override
+        public void beforeExecuteStatement(JDBCEvent event) {
+            if (event.getSQL() != null && sql != null) {
+                sql.add(event.getSQL());
+                sqlCount++;
+            }
+        }
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Department.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Department.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Department.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Department.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex1;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name="MEx1Dept")
+public class Department {
+    @Id
+    int id;
+
+    String name;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean equals(Object o) {
+        if (!(o instanceof Department))
+            return false;
+        Department other = (Department) o;
+        if (name.equals(other.name) &&
+            id == other.id)
+            return true;
+        return false;
+    } 
+
+    public int hashCode() {
+        int ret = 0;
+        ret = ret * 31 + name.hashCode();
+        ret = ret *31 + id;
+        return ret;
+    }    
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Division.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Division.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Division.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Division.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex1;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name="MEx1Division")
+public class Division {
+    @Id
+    int id;
+
+    String name;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean equals(Object o) {
+        if (!(o instanceof Division))
+            return false;
+        Division other = (Division) o;
+        if (name.equals(other.name) &&
+            id == other.id)
+            return true;
+        return false;
+    } 
+
+    public int hashCode() {
+        int ret = 0;
+        ret = ret * 31 + name.hashCode();
+        ret = ret *31 + id;
+        return ret;
+    }        
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Employee.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Employee.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Employee.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/Employee.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex1;
+
+import javax.persistence.*;
+
+import java.util.*;
+
+@Entity
+@Table(name="MEx1Emp")
+public class Employee {
+    @Id
+    int empId;
+
+    @ManyToMany
+    Map<Department, PhoneNumber> phones = 
+        new HashMap<Department, PhoneNumber>();   
+
+    public Map<Department, PhoneNumber> getPhoneNumbers() {
+        return phones;
+    }
+
+    public void addPhoneNumber(Department d, PhoneNumber phoneNumber) {
+        phones.put(d, phoneNumber);
+    }
+
+    public void removePhoneNumber(Department d) {
+        phones.remove(d);
+    }
+
+    public int getEmpId() {
+        return empId;
+    }
+
+    public void setEmpId(int empId) {
+        this.empId = empId;
+    }
+
+    public boolean equals(Object o) {
+        Employee e = (Employee) o;
+        Map<Department, PhoneNumber> map = e.getPhoneNumbers();
+        if (map.size() != phones.size())
+            return false;
+        Collection<Map.Entry<Department, PhoneNumber>> entries =
+            (Collection<Map.Entry<Department, PhoneNumber>>) phones.entrySet();
+        for (Map.Entry<Department, PhoneNumber> entry : entries) {
+            Department key = entry.getKey();
+            PhoneNumber p = entry.getValue();
+            PhoneNumber p0 = map.get(key);
+            if (p.getNumber() != p0.getNumber())
+                return false;
+        }
+        return true;
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/PhoneNumber.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/PhoneNumber.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/PhoneNumber.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/PhoneNumber.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex1;
+
+import javax.persistence.*;
+
+import java.util.*;
+
+@Entity
+@Table(name="MEx1Phone")
+public class PhoneNumber {
+    @Id int number;
+
+    @ManyToMany(mappedBy="phones")
+    Map<Division, Employee> emps = new HashMap<Division, Employee>();
+
+    public int getNumber() {
+        return number;
+    }
+
+    public void setNumber(int number) {
+        this.number = number;
+    }
+
+    public Map<Division, Employee>  getEmployees() {
+        return emps;
+    }
+
+    public void addEmployees(Division d, Employee employee) {
+        emps.put(d, employee);
+    }
+
+    public void removeEmployee(Division d) {
+        emps.remove(d);
+    }
+
+    public boolean equals(Object o) {
+        PhoneNumber p = (PhoneNumber) o;
+        Map<Division, Employee> map = p.getEmployees(); 
+        if (map.size() != emps.size())
+            return false;
+        Collection<Map.Entry<Division, Employee>> entries =
+            (Collection<Map.Entry<Division, Employee>>) emps.entrySet();
+        for (Map.Entry<Division, Employee> entry : entries) {
+            Division key = entry.getKey();
+            Employee e = entry.getValue();
+            Employee e0 = map.get(key);
+            if (e.getEmpId() != e0.getEmpId())
+                return false;
+        }
+        return true;
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/TestMany2ManyMapEx1.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/TestMany2ManyMapEx1.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/TestMany2ManyMapEx1.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex1/TestMany2ManyMapEx1.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex1;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Query;
+
+import junit.framework.Assert;
+
+import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
+import org.apache.openjpa.lib.jdbc.JDBCEvent;
+import org.apache.openjpa.lib.jdbc.JDBCListener;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+public class TestMany2ManyMapEx1 extends SingleEMFTestCase {
+
+    public int numEmployees = 2;
+    public int numPhoneNumbersPerEmployee = 2;
+
+    public Map<Integer, Employee> empMap = new HashMap<Integer, Employee>();
+    public Map<Integer, PhoneNumber> phoneMap =
+        new HashMap<Integer, PhoneNumber>();
+
+    public List<String> namedQueries = new ArrayList<String>();
+
+    public int empId = 1;
+    public int phoneId = 1;
+    public int divId = 1;
+    public int deptId = 10;
+
+    protected List<String> sql = new ArrayList<String>();
+    protected int sqlCount;
+
+    public void setUp() {
+        super.setUp(CLEAR_TABLES,
+            Department.class,
+            Division.class,
+            Employee.class,
+            PhoneNumber.class,
+            "openjpa.jdbc.JDBCListeners", 
+            new JDBCListener[] { 
+            this.new Listener() 
+        });
+
+        createObj(emf);
+    }
+
+    public void testQueryQualifiedId() throws Exception {
+        EntityManager em = emf.createEntityManager();
+        String query = "select KEY(e), p from PhoneNumber p, " +
+            " in (p.emps) e order by e.empId";
+        List rs = em.createQuery(query).getResultList();
+        Division d = (Division) ((Object[]) rs.get(0))[0];
+        PhoneNumber p = (PhoneNumber) ((Object[]) rs.get(0))[1];
+
+        String query2 = "select KEY(p) from Employee e, " +
+                " in (e.phones) p";
+        List rs2 = em.createQuery(query2).getResultList();
+        Department d2 = (Department) rs2.get(0);
+
+        em.clear();
+        String query4 = "select ENTRY(e) from PhoneNumber p, " +
+            " in (p.emps) e order by e.empId";
+        List rs4 = em.createQuery(query4).getResultList();
+        Map.Entry me = (Map.Entry) rs4.get(0);
+
+        assertTrue(d.equals(me.getKey()));
+
+        em.close();
+    }
+
+    public void testQueryObject() throws Exception {
+        queryObj(emf);
+        findObj(emf);
+    }
+
+    public List<String> getSql() {
+        return sql;
+    }
+
+    public int getSqlCount() {
+        return sqlCount;
+    }
+
+    public void createObj(EntityManagerFactory emf) {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        for (int i = 0; i < numEmployees; i++) {
+            Employee e = createEmployee(em, empId++);
+            empMap.put(e.getEmpId(), e);
+        }
+        tran.begin();
+        em.flush();
+        tran.commit();
+        em.close();
+    }
+
+    public Employee createEmployee(EntityManager em, int id) {
+        Employee e = new Employee();
+        e.setEmpId(id);
+        for (int i = 0; i < numPhoneNumbersPerEmployee; i++) { 
+            PhoneNumber phoneNumber = new PhoneNumber();
+            phoneNumber.setNumber(phoneId++);
+            Division div = createDivision(em, divId++);
+            Department dept = createDepartment(em, deptId++);
+            phoneNumber.addEmployees(div, e);
+            e.addPhoneNumber(dept, phoneNumber);
+            phoneMap.put(phoneNumber.getNumber(), phoneNumber);
+            em.persist(phoneNumber);
+            em.persist(dept);
+            em.persist(div);
+        }
+        em.persist(e);
+        return e;
+    }
+
+    public Division createDivision(EntityManager em, int id) {
+        Division d = new Division();
+        d.setId(id);
+        d.setName("d" + id);
+        return d;
+    }
+
+    public Department createDepartment(EntityManager em, int id) {
+        Department d = new Department();
+        d.setId(id);
+        d.setName("dept" + id);
+        return d;
+    }
+
+    public void findObj(EntityManagerFactory emf) throws Exception {
+        EntityManager em = emf.createEntityManager();
+        Employee e = em.find(Employee.class, 1);
+        assertEmployee(e);
+
+        PhoneNumber p = em.find(PhoneNumber.class, 1);
+        assertPhoneNumber(p);
+        em.close();
+    }
+
+    public void queryObj(EntityManagerFactory emf) throws Exception {
+        queryEmployee(emf);
+        queryPhoneNumber(emf);
+    }
+
+    public void queryPhoneNumber(EntityManagerFactory emf) throws Exception {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        tran.begin();
+        Query q = em.createQuery("select p from PhoneNumber p");
+        List<PhoneNumber> ps = q.getResultList();
+        for (PhoneNumber p : ps) {
+            assertPhoneNumber(p);
+        }
+        tran.commit();
+        em.close();
+    }
+
+    public void queryEmployee(EntityManagerFactory emf) throws Exception {
+        EntityManager em = emf.createEntityManager();
+        EntityTransaction tran = em.getTransaction();
+        tran.begin();
+        Query q = em.createQuery("select e from Employee e");
+        List<Employee> es = q.getResultList();
+        for (Employee e : es) {
+            assertEmployee(e);
+        }
+        tran.commit();
+        em.close();
+    }
+
+    public void assertEmployee(Employee e) throws Exception {
+        int id = e.getEmpId();
+        Employee e0 = empMap.get(id);
+        Map<Department, PhoneNumber> phones0 = e0.getPhoneNumbers();
+        Map<Department, PhoneNumber> phones = e.getPhoneNumbers();
+        Assert.assertEquals(phones0.size(), phones.size());
+        checkPhoneMap(phones0, phones);
+    }
+
+    public void assertPhoneNumber(PhoneNumber p) throws Exception {
+        int number = p.getNumber();
+        PhoneNumber p0 = phoneMap.get(number);
+        Map<Division, Employee> es0 = p0.getEmployees();
+        Map<Division, Employee> es = p.getEmployees();
+        Assert.assertEquals(es0.size(), es.size());
+        checkEmpMap(es0, es);
+    }
+
+    public void checkPhoneMap(Map<Department, PhoneNumber> es0, 
+        Map<Department, PhoneNumber> es) throws Exception {
+        Collection<Map.Entry<Department, PhoneNumber>> entrySets0 =
+            es0.entrySet();
+        for (Map.Entry<Department, PhoneNumber> entry0 : entrySets0) {
+            Department d0 = entry0.getKey();
+            PhoneNumber p0 = entry0.getValue();
+            PhoneNumber p = es.get(d0);
+            if (!p0.equals(p))
+                throw new Exception("Assertion failure");
+        }
+    }
+
+    public void checkEmpMap(Map<Division, Employee> es0,
+        Map<Division, Employee> es) throws Exception {
+        Collection<Map.Entry<Division, Employee>> entrySets0 = es0.entrySet();
+        for (Map.Entry<Division, Employee> entry0 : entrySets0) {
+            Division d0 = entry0.getKey();
+            Employee e0 = entry0.getValue();
+            Employee e = es.get(d0);
+            if (!e0.equals(e))
+                throw new Exception("Assertion failure");
+        }
+    }    
+
+    public class Listener extends AbstractJDBCListener {
+        @Override
+        public void beforeExecuteStatement(JDBCEvent event) {
+            if (event.getSQL() != null && sql != null) {
+                sql.add(event.getSQL());
+                sqlCount++;
+            }
+        }
+    }
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/Employee.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/Employee.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/Employee.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/Employee.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex10;
+
+import javax.persistence.*;
+
+import java.util.*;
+
+@Entity
+@Table(name="MEx10Emp")
+public class Employee {
+    @EmbeddedId
+    EmployeePK empPK;
+
+    @ManyToMany // Bidirectional
+    Map<PhonePK, PhoneNumber> phones = new HashMap<PhonePK, PhoneNumber>();
+
+    int salary;
+
+    public EmployeePK getEmpPK() {
+        return empPK;
+    }
+
+    public void setEmpPK(EmployeePK empPK) {
+        this.empPK = empPK;
+    }
+
+
+    public Map<PhonePK, PhoneNumber> getPhoneNumbers() {
+        return phones;
+    }
+
+    public void addPhoneNumber(PhonePK d, PhoneNumber phoneNumber) {
+        phones.put(d, phoneNumber);
+    }
+
+    public void removePhoneNumber(PhonePK d) {
+        phones.remove(d);
+    }
+
+    public int getSalary() {
+        return salary;
+    }
+
+    public void setSalary(int salary) {
+        this.salary = salary;
+    }
+
+    public boolean equals(Object o) {
+        Employee e = (Employee) o;
+        Map<PhonePK, PhoneNumber> map = e.getPhoneNumbers();
+        if (map.size() != phones.size())
+            return false;
+        Collection<Map.Entry<PhonePK, PhoneNumber>> entries =
+            (Collection<Map.Entry<PhonePK, PhoneNumber>>) phones.entrySet();
+        for (Map.Entry<PhonePK, PhoneNumber> entry : entries) {
+            PhonePK key = entry.getKey();
+            PhoneNumber p = entry.getValue();
+            PhoneNumber p0 = map.get(key);
+            if (!p.getPhonePK().equals(p0.getPhonePK()))
+                return false;
+        }
+        return true;
+    }    
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/EmployeePK.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/EmployeePK.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/EmployeePK.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/EmployeePK.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex10;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.*;
+
+@Embeddable
+public class EmployeePK implements Serializable {
+    String name;
+    Date bDay;
+
+    public EmployeePK() {}
+    public EmployeePK(String name, Date bDay) {
+        this.name = name;
+        this.bDay = bDay;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof EmployeePK))
+            return false;
+        EmployeePK pk = (EmployeePK) o;
+        if (pk.name.equals(name) &&
+            pk.bDay.equals(bDay))
+            return true;    
+        return false;
+    }
+
+    public int hashCode() {
+        int code = 0;
+        code += name.hashCode();
+        code += bDay.hashCode();
+        return code;
+    }    
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhoneNumber.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhoneNumber.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhoneNumber.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhoneNumber.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex10;
+
+import javax.persistence.*;
+
+import java.util.*;
+
+@Entity
+@Table(name="MEx10Phone")
+public class PhoneNumber {
+    @EmbeddedId
+    PhonePK phonePK;
+
+    @ManyToMany(mappedBy="phones")
+    Map<EmployeePK, Employee> emps = new HashMap<EmployeePK, Employee>();
+
+    int room;    
+
+    public PhonePK getPhonePK() {
+        return phonePK;
+    }
+
+    public void setPhonePK(PhonePK phonePK) {
+        this.phonePK = phonePK;
+    }
+
+    public Map<EmployeePK, Employee>  getEmployees() {
+        return emps;
+    }
+
+    public void addEmployees(EmployeePK d, Employee employee) {
+        emps.put(d, employee);
+    }
+
+    public void removeEmployee(EmployeePK d) {
+        emps.remove(d);
+    }
+
+    public int getRoom() {
+        return room;
+    }
+
+    public void setRoom(int room) {
+        this.room = room;
+    }
+
+    public boolean equals(Object o) {
+        PhoneNumber p = (PhoneNumber) o;
+        Map<EmployeePK, Employee> map = p.getEmployees();
+        if (map.size() != emps.size())
+            return false;
+        Collection<Map.Entry<EmployeePK, Employee>> entries =
+            (Collection<Map.Entry<EmployeePK, Employee>>) emps.entrySet();
+        for (Map.Entry<EmployeePK, Employee> entry : entries) {
+            EmployeePK key = entry.getKey();
+            Employee e0 = map.get(key);
+            Employee e = emps.get(key);
+            if (!e.getEmpPK().equals(e0.getEmpPK()))
+                return false;
+        }
+        return true;
+    }    
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhonePK.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhonePK.java?rev=750517&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhonePK.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/maps/m2mmapex10/PhonePK.java Thu Mar  5 17:30:20 2009
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.maps.m2mmapex10;
+
+import java.io.Serializable;
+
+import javax.persistence.*;
+
+@Embeddable
+public class PhonePK implements Serializable {
+    String areaCode;
+    String phoneNum;
+
+    public PhonePK() {}
+    public PhonePK(String areaCode, String phoneNum) {
+        this.areaCode = areaCode;
+        this.phoneNum = phoneNum;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof PhonePK))
+            return false;
+        PhonePK pk = (PhonePK) o;
+        if (pk.areaCode.equals(areaCode) &&
+            pk.phoneNum.equals(phoneNum))
+            return true;    
+        return false;
+    }
+
+    public int hashCode() {
+        int code = 0;
+        code = code * 31 + areaCode.hashCode();
+        code = code * 31 + phoneNum.hashCode();
+        return code;
+    }
+}