001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.convert;
017:
018: import java.lang.reflect.Modifier;
019: import java.util.HashMap;
020: import java.util.Map;
021: import java.util.StringTokenizer;
022: import java.util.Map.Entry;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.directwebremoting.dwrp.MapOutboundVariable;
027: import org.directwebremoting.dwrp.ObjectJsonOutboundVariable;
028: import org.directwebremoting.dwrp.ObjectNonJsonOutboundVariable;
029: import org.directwebremoting.dwrp.ParseUtil;
030: import org.directwebremoting.dwrp.ProtocolConstants;
031: import org.directwebremoting.extend.Converter;
032: import org.directwebremoting.extend.ConverterManager;
033: import org.directwebremoting.extend.InboundContext;
034: import org.directwebremoting.extend.InboundVariable;
035: import org.directwebremoting.extend.MarshallException;
036: import org.directwebremoting.extend.OutboundContext;
037: import org.directwebremoting.extend.OutboundVariable;
038: import org.directwebremoting.extend.TypeHintContext;
039: import org.directwebremoting.util.JavascriptUtil;
040: import org.directwebremoting.util.LocalUtil;
041: import org.directwebremoting.util.Messages;
042:
043: /**
044: * An implementation of Converter for Maps.
045: * @author Joe Walker [joe at getahead dot ltd dot uk]
046: */
047: public class MapConverter implements Converter {
048: /* (non-Javadoc)
049: * @see org.directwebremoting.Converter#setConverterManager(org.directwebremoting.ConverterManager)
050: */
051: public void setConverterManager(ConverterManager converterManager) {
052: this .converterManager = converterManager;
053: }
054:
055: /* (non-Javadoc)
056: * @see org.directwebremoting.Converter#convertInbound(java.lang.Class, org.directwebremoting.InboundVariable, org.directwebremoting.InboundContext)
057: */
058: @SuppressWarnings("unchecked")
059: public Object convertInbound(Class<?> paramType,
060: InboundVariable data, InboundContext inctx)
061: throws MarshallException {
062: String value = data.getValue();
063:
064: // If the text is null then the whole bean is null
065: if (value.trim().equals(ProtocolConstants.INBOUND_NULL)) {
066: return null;
067: }
068:
069: if (!value.startsWith(ProtocolConstants.INBOUND_MAP_START)) {
070: throw new IllegalArgumentException(Messages.getString(
071: "MapConverter.FormatError",
072: ProtocolConstants.INBOUND_MAP_START));
073: }
074:
075: if (!value.endsWith(ProtocolConstants.INBOUND_MAP_END)) {
076: throw new IllegalArgumentException(Messages.getString(
077: "MapConverter.FormatError",
078: ProtocolConstants.INBOUND_MAP_END));
079: }
080:
081: value = value.substring(1, value.length() - 1);
082:
083: try {
084: // Maybe we ought to check that the paramType isn't expecting a more
085: // distinct type of Map and attempt to create that?
086: Map<Object, Object> map;
087:
088: // If paramType is concrete then just use whatever we've got.
089: if (!paramType.isInterface()
090: && !Modifier.isAbstract(paramType.getModifiers())) {
091: // If there is a problem creating the type then we have no way
092: // of completing this - they asked for a specific type and we
093: // can't create that type. I don't know of a way of finding
094: // subclasses that might be instaniable so we accept failure.
095: map = (Map<Object, Object>) paramType.newInstance();
096: } else {
097: map = new HashMap<Object, Object>();
098: }
099:
100: // Get the extra type info
101: TypeHintContext thc = inctx.getCurrentTypeHintContext();
102:
103: TypeHintContext keyThc = thc.createChildContext(0);
104: Class<?> keyType = keyThc.getExtraTypeInfo();
105:
106: TypeHintContext valThc = thc.createChildContext(1);
107: Class<?> valType = valThc.getExtraTypeInfo();
108:
109: // We should put the new object into the working map in case it
110: // is referenced later nested down in the conversion process.
111: inctx.addConverted(data, paramType, map);
112: InboundContext incx = data.getLookup();
113:
114: // Loop through the property declarations
115: StringTokenizer st = new StringTokenizer(value, ",");
116: int size = st.countTokens();
117: for (int i = 0; i < size; i++) {
118: String token = st.nextToken();
119: if (token.trim().length() == 0) {
120: continue;
121: }
122:
123: int colonpos = token
124: .indexOf(ProtocolConstants.INBOUND_MAP_ENTRY);
125: if (colonpos == -1) {
126: throw new MarshallException(
127: paramType,
128: Messages
129: .getString(
130: "MapConverter.MissingSeparator",
131: ProtocolConstants.INBOUND_MAP_ENTRY,
132: token));
133: }
134:
135: // Convert the value part of the token by splitting it into the
136: // type and value (as passed in by Javascript)
137: String valStr = token.substring(colonpos + 1).trim();
138: String[] splitIv = ParseUtil.splitInbound(valStr);
139: String splitIvValue = splitIv[LocalUtil.INBOUND_INDEX_VALUE];
140: String splitIvType = splitIv[LocalUtil.INBOUND_INDEX_TYPE];
141: InboundVariable valIv = new InboundVariable(incx, null,
142: splitIvType, splitIvValue);
143: valIv.dereference();
144: Object val = converterManager.convertInbound(valType,
145: valIv, inctx, valThc);
146:
147: // Keys (unlike values) do not have type info passed with them
148: // Could we have recursive key? - I don't think so because keys
149: // must be strings in Javascript
150: String keyStr = token.substring(0, colonpos).trim();
151: //String[] keySplit = LocalUtil.splitInbound(keyStr);
152: //InboundVariable keyIv = new InboundVariable(incx, splitIv[LocalUtil.INBOUND_INDEX_TYPE], splitIv[LocalUtil.INBOUND_INDEX_VALUE]);
153: InboundVariable keyIv = new InboundVariable(incx, null,
154: ProtocolConstants.TYPE_STRING, keyStr);
155: keyIv.dereference();
156:
157: Object key = converterManager.convertInbound(keyType,
158: keyIv, inctx, keyThc);
159:
160: map.put(key, val);
161: }
162:
163: return map;
164: } catch (MarshallException ex) {
165: throw ex;
166: } catch (Exception ex) {
167: throw new MarshallException(paramType, ex);
168: }
169: }
170:
171: /* (non-Javadoc)
172: * @see org.directwebremoting.Converter#convertOutbound(java.lang.Object, org.directwebremoting.OutboundContext)
173: */
174: @SuppressWarnings("unchecked")
175: public OutboundVariable convertOutbound(Object data,
176: OutboundContext outctx) throws MarshallException {
177: // First we just collect our converted children
178: //noinspection unchecked
179: Map<String, OutboundVariable> ovs = LocalUtil.classNewInstance(
180: "OrderedConvertOutbound", "java.util.LinkedHashMap",
181: Map.class);
182: if (ovs == null) {
183: ovs = new HashMap<String, OutboundVariable>();
184: }
185:
186: MapOutboundVariable ov;
187: if (outctx.isJsonMode()) {
188: ov = new ObjectJsonOutboundVariable();
189: } else {
190: ov = new ObjectNonJsonOutboundVariable(outctx, null);
191: }
192: outctx.put(data, ov);
193:
194: // Loop through the map outputting any init code and collecting
195: // converted outbound variables into the ovs map
196: Map<Object, Object> map = (Map<Object, Object>) data;
197: for (Entry<Object, Object> entry : map.entrySet()) {
198: Object key = entry.getKey();
199: Object value = entry.getValue();
200:
201: // It would be nice to check for Enums here
202: if (!(key instanceof String) && !sentNonStringWarning) {
203: log
204: .warn("--Javascript does not support non string keys. Converting '"
205: + key.getClass().getName()
206: + "' using toString()");
207: sentNonStringWarning = true;
208: }
209:
210: String outkey = JavascriptUtil.escapeJavaScript(key
211: .toString());
212:
213: /*
214: OutboundVariable ovkey = converterManager.convertOutbound(key, outctx);
215: buffer.append(ovkey.getInitCode());
216: outkey = ovkey.getAssignCode();
217: */
218:
219: OutboundVariable nested = converterManager.convertOutbound(
220: value, outctx);
221:
222: ovs.put(outkey, nested);
223: }
224:
225: ov.setChildren(ovs);
226:
227: return ov;
228: }
229:
230: /**
231: * We don't want to give the non-string warning too many times.
232: */
233: private static boolean sentNonStringWarning = false;
234:
235: /**
236: * To forward marshalling requests
237: */
238: private ConverterManager converterManager = null;
239:
240: /**
241: * The log stream
242: */
243: private static final Log log = LogFactory
244: .getLog(MapConverter.class);
245: }
|