View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.integration.beans;
21  
22  import java.beans.PropertyEditor;
23  import java.util.Collection;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  /**
30   * A {@link PropertyEditor} which converts a {@link String} into
31   * a {@link Collection} and vice versa.
32   *
33   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
34   */
35  public class MapEditor extends AbstractPropertyEditor {
36      static final Pattern ELEMENT = Pattern.compile("([,\\s]+)|"
37              + // Entry delimiter
38              "(\\s*=\\s*)|"
39              + // Key-Value delimiter
40              "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|"
41              + "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)");
42  
43      private final Class<?> keyType;
44  
45      private final Class<?> valueType;
46  
47      public MapEditor(Class<?> keyType, Class<?> valueType) {
48          if (keyType == null) {
49              throw new IllegalArgumentException("keyType");
50          }
51          if (valueType == null) {
52              throw new IllegalArgumentException("valueType");
53          }
54          this.keyType = keyType;
55          this.valueType = valueType;
56          getKeyEditor();
57          getValueEditor();
58          setTrimText(false);
59      }
60  
61      private PropertyEditor getKeyEditor() {
62          PropertyEditor e = PropertyEditorFactory.getInstance(keyType);
63          if (e == null) {
64              throw new IllegalArgumentException("No key " + PropertyEditor.class.getSimpleName() + " found for "
65                      + keyType.getSimpleName() + '.');
66          }
67          return e;
68      }
69  
70      private PropertyEditor getValueEditor() {
71          PropertyEditor e = PropertyEditorFactory.getInstance(valueType);
72          if (e == null) {
73              throw new IllegalArgumentException("No value " + PropertyEditor.class.getSimpleName() + " found for "
74                      + valueType.getSimpleName() + '.');
75          }
76          return e;
77      }
78  
79      @Override
80      @SuppressWarnings("unchecked")
81      protected final String toText(Object value) {
82          StringBuilder buf = new StringBuilder();
83          for (Object o : ((Map) value).entrySet()) {
84              Map.Entry entry = (Map.Entry) o;
85              Object ekey = entry.getKey();
86              Object evalue = entry.getValue();
87  
88              PropertyEditor ekeyEditor = PropertyEditorFactory.getInstance(ekey);
89              if (ekeyEditor == null) {
90                  throw new IllegalArgumentException("No key " + PropertyEditor.class.getSimpleName() + " found for "
91                          + ekey.getClass().getSimpleName() + '.');
92              }
93              ekeyEditor.setValue(ekey);
94  
95              PropertyEditor evalueEditor = PropertyEditorFactory.getInstance(evalue);
96              if (evalueEditor == null) {
97                  throw new IllegalArgumentException("No value " + PropertyEditor.class.getSimpleName() + " found for "
98                          + evalue.getClass().getSimpleName() + '.');
99              }
100             ekeyEditor.setValue(ekey);
101             evalueEditor.setValue(evalue);
102 
103             // TODO normalize.
104             String keyString = ekeyEditor.getAsText();
105             String valueString = evalueEditor.getAsText();
106             buf.append(keyString);
107             buf.append(" = ");
108             buf.append(valueString);
109             buf.append(", ");
110         }
111 
112         // Remove the last delimiter.
113         if (buf.length() >= 2) {
114             buf.setLength(buf.length() - 2);
115         }
116         return buf.toString();
117     }
118 
119     @Override
120     protected final Object toValue(String text) throws IllegalArgumentException {
121         PropertyEditor keyEditor = getKeyEditor();
122         PropertyEditor valueEditor = getValueEditor();
123         Map<Object, Object> answer = newMap();
124         Matcher m = ELEMENT.matcher(text);
125         TokenType lastTokenType = TokenType.ENTRY_DELIM;
126         Object key = null;
127         Object value = null;
128 
129         while (m.find()) {
130             if (m.group(1) != null) {
131                 switch (lastTokenType) {
132                 case VALUE:
133                 case ENTRY_DELIM:
134                     break;
135                 default:
136                     throw new IllegalArgumentException("Unexpected entry delimiter: " + text);
137                 }
138 
139                 lastTokenType = TokenType.ENTRY_DELIM;
140                 continue;
141             }
142 
143             if (m.group(2) != null) {
144                 if (lastTokenType != TokenType.KEY) {
145                     throw new IllegalArgumentException("Unexpected key-value delimiter: " + text);
146                 }
147 
148                 lastTokenType = TokenType.KEY_VALUE_DELIM;
149                 continue;
150             }
151 
152             // TODO escape here.
153             String region = m.group();
154 
155             if (m.group(3) != null || m.group(4) != null) {
156                 // Skip the last '"'.
157                 m.region(m.end() + 1, m.regionEnd());
158             }
159 
160             switch (lastTokenType) {
161             case ENTRY_DELIM:
162                 keyEditor.setAsText(region);
163                 key = keyEditor.getValue();
164                 lastTokenType = TokenType.KEY;
165                 break;
166             case KEY_VALUE_DELIM:
167                 valueEditor.setAsText(region);
168                 value = valueEditor.getValue();
169                 lastTokenType = TokenType.VALUE;
170                 answer.put(key, value);
171                 break;
172             case KEY:
173             case VALUE:
174                 throw new IllegalArgumentException("Unexpected key or value: " + text);
175             }
176         }
177 
178         return answer;
179     }
180 
181     protected Map<Object, Object> newMap() {
182         return new LinkedHashMap<Object, Object>();
183     }
184 
185     private static enum TokenType {
186         ENTRY_DELIM, KEY_VALUE_DELIM, KEY, VALUE,
187     }
188 }