--- /dev/null
+/*--------------------------------------------------------------------------
+ * Copyright 2009 Taro L. Saito
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *--------------------------------------------------------------------------*/
+//--------------------------------------
+// XerialJ
+//
+// MapEntry.java
+// Since: May 21, 2009 2:39:57 PM
+//
+// $URL$
+// $Author$
+//--------------------------------------
+package org.xerial.lens;
+
+public class MapEntry
+{
+ public Object key;
+ public Object value;
+}
import org.xerial.json.JSONString;\r
import org.xerial.json.JSONWriter;\r
import org.xerial.util.Pair;\r
+import org.xerial.util.bean.BeanUtil;\r
import org.xerial.util.bean.TypeInfo;\r
import org.xerial.util.log.Logger;\r
import org.xerial.util.reflect.ReflectionUtil;\r
return Collections.unmodifiableList(getterContainer);\r
}\r
\r
- public boolean hasParameters()\r
+ public boolean hasAttributes()\r
{\r
return !getterContainer.isEmpty();\r
}\r
// scan methods\r
for (Method eachMethod : targetType.getMethods())\r
{\r
-\r
String methodName = eachMethod.getName();\r
String paramName = pickPropertyName(methodName);\r
\r
{\r
case 1:\r
{\r
+ Class< ? > parentOfTheSetter = eachMethod.getDeclaringClass();\r
+ if (TypeInfo.isCollection(parentOfTheSetter) || TypeInfo.isMap(parentOfTheSetter))\r
+ break;\r
+\r
addNewSetter(setterContainer, paramName, eachMethod);\r
break;\r
}\r
if (relName == null)\r
{\r
// infer relation node names\r
- relName = new Pair<String, String>(getCanonicalParameterName(argTypes[0].getSimpleName()),\r
- getCanonicalParameterName(argTypes[1].getSimpleName()));\r
+ if (TypeInfo.isMap(eachMethod.getDeclaringClass()))\r
+ {\r
+\r
+ Class< ? >[] mapElementType = BeanUtil.resolveActualTypeOfMapElement(targetType, eachMethod\r
+ .getParameterTypes());\r
+\r
+ // map.put(Key, Value)\r
+ setterContainer\r
+ .add(ParameterSetter.newMapEntrySetter(mapElementType[0], mapElementType[1]));\r
+\r
+ // (entry, key)\r
+ setterContainer.add(ParameterSetter.newKeySetter(mapElementType[0]));\r
+ // (entry, value)\r
+ setterContainer.add(ParameterSetter.newValueSetter(mapElementType[1]));\r
+ continue;\r
+ }\r
+ else\r
+ {\r
+ relName = new Pair<String, String>(getCanonicalParameterName(argTypes[0].getSimpleName()),\r
+ getCanonicalParameterName(argTypes[1].getSimpleName()));\r
+ }\r
}\r
\r
relationSetterContainer.add(RelationSetter.newRelationSetter(relName.getFirst(), relName\r
}\r
\r
}\r
-\r
}\r
\r
private static void addNewSetter(List<ParameterSetter> setterContainer, String paramPart, Method m)\r
\r
public static String getCanonicalParameterName(String paramName)\r
{\r
+ if (paramName == null)\r
+ return paramName;\r
+\r
Matcher m = paramNameReplacePattern.matcher(paramName);\r
return m.replaceAll("").toLowerCase();\r
}\r
\r
if (TypeInfo.isCollection(c))\r
{\r
- if (lens.hasParameters())\r
+ Collection< ? > collection = (Collection< ? >) obj;\r
+ boolean hasAttributes = lens.hasAttributes();\r
+\r
+ if (hasAttributes)\r
{\r
json.startObject();\r
outputParemters(json, obj);\r
\r
- json.startArray("entry");\r
+ if (!collection.isEmpty())\r
+ json.startArray("entry");\r
}\r
- else\r
+ else if (!collection.isEmpty())\r
json.startArray();\r
\r
- Collection< ? > collection = (Collection< ? >) obj;\r
for (Object elem : collection)\r
{\r
toJSON(json, elem);\r
}\r
\r
- json.endArray();\r
+ if (!collection.isEmpty())\r
+ json.endArray();\r
\r
- if (lens.hasParameters())\r
+ if (hasAttributes)\r
json.endObject();\r
\r
}\r
else if (TypeInfo.isMap(c))\r
{\r
- if (lens.hasParameters())\r
+ Map< ? , ? > map = (Map< ? , ? >) obj;\r
+ boolean hasAttributes = lens.hasAttributes();\r
+\r
+ if (hasAttributes)\r
{\r
json.startObject();\r
outputParemters(json, obj);\r
\r
- json.startArray("entry");\r
+ if (!map.isEmpty())\r
+ json.startArray("entry");\r
}\r
- else\r
+ else if (!map.isEmpty())\r
json.startArray();\r
\r
- Map< ? , ? > map = (Map< ? , ? >) obj;\r
-\r
- json.startArray();\r
for (Entry< ? , ? > each : map.entrySet())\r
{\r
json.startObject();\r
json.putObject("value", each.getValue());\r
json.endObject();\r
}\r
- json.endArray();\r
- json.endArray();\r
\r
- if (lens.hasParameters())\r
+ if (!map.isEmpty())\r
+ json.endArray();\r
+\r
+ if (hasAttributes)\r
json.endObject();\r
}\r
else\r
import org.xerial.core.XerialError;
import org.xerial.core.XerialErrorCode;
import org.xerial.core.XerialException;
+import org.xerial.lens.ParameterSetter.MapEntryBinder;
import org.xerial.relation.Node;
import org.xerial.relation.query.AmoebaJoinHandlerBase;
import org.xerial.relation.query.QuerySet;
public void build(Class< ? > targetType, String alias)
{
- if (TypeInfo.isBasicType(targetType) || TypeInfo.isCollection(targetType))
+ // TODO use context-based schema -> binder mapping
+
+ if (TypeInfo.isBasicType(targetType) || TypeInfo.isCollection(targetType) || targetType == MapEntry.class)
return;
if (processedClasses.contains(targetType))
for (ParameterSetter each : lens.getSetterList())
{
+ if (each.getClass() == MapEntryBinder.class)
+ {
+ SchemaBuilder builder = new SchemaBuilder();
+ builder.add("entry");
+ builder.add(each.getParameterName());
+ Schema s = builder.build();
+ qs.addQueryTarget(s);
+ schema2binder.put(s, new AttributeBinder(MapEntry.class, each));
+ continue;
+ }
+
build(each.getParameterType(), each.getParameterName());
SchemaBuilder builder = new SchemaBuilder();
\r
import java.lang.reflect.Field;\r
import java.lang.reflect.Method;\r
+import java.util.Map;\r
\r
+import org.xerial.core.XerialError;\r
+import org.xerial.core.XerialErrorCode;\r
import org.xerial.core.XerialException;\r
+import org.xerial.util.log.Logger;\r
import org.xerial.util.reflect.ReflectionUtil;\r
\r
/**\r
*/\r
public abstract class ParameterSetter\r
{\r
+ private static Logger _logger = Logger.getLogger(ParameterSetter.class);\r
+\r
private final Class< ? > parameterType;\r
private final String parameterName;\r
\r
return new MethodSetter(parameterType, parameterName, setterMethod);\r
}\r
\r
+ public static ParameterSetter newKeySetter(Class< ? > keyType)\r
+ {\r
+ return new MapEntryBinder(keyType, "key");\r
+ }\r
+\r
+ public static ParameterSetter newValueSetter(Class< ? > valueType)\r
+ {\r
+ return new MapEntryBinder(valueType, "value");\r
+ }\r
+\r
+ public static ParameterSetter newMapEntrySetter(Class< ? > keyType, Class< ? > valueType)\r
+ {\r
+ return new MapEntrySetter(keyType, valueType);\r
+ }\r
+\r
private static class FieldSetter extends ParameterSetter\r
{\r
private final Field targetField;\r
\r
}\r
\r
+ static class MapEntryBinder extends ParameterSetter\r
+ {\r
+ private final Field targetField;\r
+\r
+ public MapEntryBinder(Class< ? > parameterType, String parameterName)\r
+ {\r
+ super(parameterType, parameterName);\r
+\r
+ try\r
+ {\r
+ targetField = MapEntry.class.getField(parameterName);\r
+ }\r
+ catch (Exception e)\r
+ {\r
+ throw new XerialError(XerialErrorCode.INVALID_STATE, e);\r
+ }\r
+\r
+ }\r
+\r
+ @Override\r
+ public void bind(Object entry, Object key) throws XerialException\r
+ {\r
+ ReflectionUtil.setFieldValue(entry, targetField, key);\r
+ }\r
+\r
+ }\r
+\r
+ private static class MapEntrySetter extends ParameterSetter\r
+ {\r
+ final Class< ? > keyType;\r
+ final Class< ? > valueType;\r
+\r
+ public MapEntrySetter(Class< ? > keyType, Class< ? > valueType)\r
+ {\r
+ super(MapEntry.class, "entry");\r
+\r
+ this.keyType = keyType;\r
+ this.valueType = valueType;\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public void bind(Object mapObject, Object entryObject) throws XerialException\r
+ {\r
+ Map map = Map.class.cast(mapObject);\r
+ if (map == null)\r
+ {\r
+ _logger.warn("not a map type: " + mapObject);\r
+ return;\r
+ }\r
+\r
+ MapEntry entry = MapEntry.class.cast(entryObject);\r
+ if (entry == null)\r
+ {\r
+ _logger.warn("not a map entry type: " + entryObject);\r
+ return;\r
+ }\r
+\r
+ try\r
+ {\r
+ if (entry.key != null)\r
+ map.put(keyType.cast(entry.key), valueType.cast(entry.value));\r
+ }\r
+ catch (ClassCastException e)\r
+ {\r
+ _logger.warn("cannot convert type: " + e);\r
+ return;\r
+ }\r
+ }\r
+\r
+ }\r
+\r
}\r
return orig;\r
}\r
\r
- private static Class< ? >[] resolveActualTypeOfMapElement(Type type, Class< ? >[] orig)\r
+ public static Class< ? >[] resolveActualTypeOfMapElement(Type type, Class< ? >[] orig)\r
{\r
ParameterizedType pt = getParentParameterizedType(type, Map.class);\r
if (pt != null)\r
import static org.junit.Assert.*;
+import java.io.StringReader;
import java.util.TreeMap;
import org.junit.After;
public static class ExtMap extends TreeMap<Integer, String>
{
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
public String name = "ext-map";
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof ExtMap))
+ return false;
+
+ ExtMap other = (ExtMap) o;
+
+ return name.equals(other.name) && super.equals(other);
+
+ }
}
@Test
ExtMap extMap = new ExtMap();
extMap.put(1, "hello");
extMap.put(10, "world");
- _logger.info(ObjectLens.toJSON(extMap));
+ String json = ObjectLens.toJSON(extMap);
+ _logger.debug(json);
+
+ ExtMap extMap2 = Lens.loadJSON(ExtMap.class, new StringReader(json));
+ _logger.debug(ObjectLens.toJSON(extMap2));
+
+ assertEquals(extMap, extMap2);
}
}