001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.integration.beans;
021:
022: import java.beans.PropertyEditor;
023: import java.util.Collection;
024: import java.util.LinkedHashMap;
025: import java.util.Map;
026: import java.util.regex.Matcher;
027: import java.util.regex.Pattern;
028:
029: /**
030: * A {@link PropertyEditor} which converts a {@link String} into
031: * a {@link Collection} and vice versa.
032: *
033: * @author The Apache MINA Project (dev@mina.apache.org)
034: * @version $Revision: 601229 $, $Date: 2007-12-05 00:13:18 -0700 (Wed, 05 Dec 2007) $
035: */
036: public class MapEditor extends AbstractPropertyEditor {
037: static final Pattern ELEMENT = Pattern.compile("([,\\s]+)|"
038: + // Entry delimiter
039: "(\\s*=\\s*)|"
040: + // Key-Value delimiter
041: "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|"
042: + "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|"
043: + "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)");
044:
045: private final Class<?> keyType;
046: private final Class<?> valueType;
047:
048: public MapEditor(Class<?> keyType, Class<?> valueType) {
049: if (keyType == null) {
050: throw new NullPointerException("keyType");
051: }
052: if (valueType == null) {
053: throw new NullPointerException("valueType");
054: }
055: this .keyType = keyType;
056: this .valueType = valueType;
057: getKeyEditor();
058: getValueEditor();
059: setTrimText(false);
060: }
061:
062: private PropertyEditor getKeyEditor() {
063: PropertyEditor e = PropertyEditorFactory.getInstance(keyType);
064: if (e == null) {
065: throw new IllegalArgumentException("No key "
066: + PropertyEditor.class.getSimpleName()
067: + " found for " + keyType.getSimpleName() + '.');
068: }
069: return e;
070: }
071:
072: private PropertyEditor getValueEditor() {
073: PropertyEditor e = PropertyEditorFactory.getInstance(valueType);
074: if (e == null) {
075: throw new IllegalArgumentException("No value "
076: + PropertyEditor.class.getSimpleName()
077: + " found for " + valueType.getSimpleName() + '.');
078: }
079: return e;
080: }
081:
082: @Override
083: @SuppressWarnings("unchecked")
084: protected final String toText(Object value) {
085: StringBuilder buf = new StringBuilder();
086: for (Object o : ((Map) value).entrySet()) {
087: Map.Entry entry = (Map.Entry) o;
088: Object ekey = entry.getKey();
089: Object evalue = entry.getValue();
090:
091: PropertyEditor ekeyEditor = PropertyEditorFactory
092: .getInstance(ekey);
093: if (ekeyEditor == null) {
094: throw new IllegalArgumentException("No key "
095: + PropertyEditor.class.getSimpleName()
096: + " found for "
097: + ekey.getClass().getSimpleName() + '.');
098: }
099: ekeyEditor.setValue(ekey);
100:
101: PropertyEditor evalueEditor = PropertyEditorFactory
102: .getInstance(evalue);
103: if (evalueEditor == null) {
104: throw new IllegalArgumentException("No value "
105: + PropertyEditor.class.getSimpleName()
106: + " found for "
107: + evalue.getClass().getSimpleName() + '.');
108: }
109: ekeyEditor.setValue(ekey);
110: evalueEditor.setValue(evalue);
111:
112: // TODO normalize.
113: String keyString = ekeyEditor.getAsText();
114: String valueString = evalueEditor.getAsText();
115: buf.append(keyString);
116: buf.append(" = ");
117: buf.append(valueString);
118: buf.append(", ");
119: }
120:
121: // Remove the last delimiter.
122: if (buf.length() >= 2) {
123: buf.setLength(buf.length() - 2);
124: }
125: return buf.toString();
126: }
127:
128: @Override
129: protected final Object toValue(String text)
130: throws IllegalArgumentException {
131: PropertyEditor keyEditor = getKeyEditor();
132: PropertyEditor valueEditor = getValueEditor();
133: Map<Object, Object> answer = newMap();
134: Matcher m = ELEMENT.matcher(text);
135: TokenType lastTokenType = TokenType.ENTRY_DELIM;
136: Object key = null;
137: Object value = null;
138:
139: while (m.find()) {
140: if (m.group(1) != null) {
141: switch (lastTokenType) {
142: case VALUE:
143: case ENTRY_DELIM:
144: break;
145: default:
146: throw new IllegalArgumentException(
147: "Unexpected entry delimiter: " + text);
148: }
149:
150: lastTokenType = TokenType.ENTRY_DELIM;
151: continue;
152: }
153:
154: if (m.group(2) != null) {
155: if (lastTokenType != TokenType.KEY) {
156: throw new IllegalArgumentException(
157: "Unexpected key-value delimiter: " + text);
158: }
159:
160: lastTokenType = TokenType.KEY_VALUE_DELIM;
161: continue;
162: }
163:
164: // TODO escape here.
165: String region = m.group();
166:
167: if (m.group(3) != null || m.group(4) != null) {
168: // Skip the last '"'.
169: m.region(m.end() + 1, m.regionEnd());
170: }
171:
172: switch (lastTokenType) {
173: case ENTRY_DELIM:
174: keyEditor.setAsText(region);
175: key = keyEditor.getValue();
176: lastTokenType = TokenType.KEY;
177: break;
178: case KEY_VALUE_DELIM:
179: valueEditor.setAsText(region);
180: value = valueEditor.getValue();
181: lastTokenType = TokenType.VALUE;
182: answer.put(key, value);
183: break;
184: case KEY:
185: case VALUE:
186: throw new IllegalArgumentException(
187: "Unexpected key or value: " + text);
188: }
189: }
190:
191: return answer;
192: }
193:
194: protected Map<Object, Object> newMap() {
195: return new LinkedHashMap<Object, Object>();
196: }
197:
198: private static enum TokenType {
199: ENTRY_DELIM, KEY_VALUE_DELIM, KEY, VALUE,
200: }
201: }
|