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(
37              "([,\\s]+)|" + // Entry delimiter
38              "(\\s*=\\s*)|" + // Key-Value delimiter
39              "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|" +
40              "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" +
41              "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)");
42      
43      private final Class<?> keyType;
44      private final Class<?> valueType;
45      
46      public MapEditor(Class<?> keyType, Class<?> valueType) {
47          if (keyType == null) {
48              throw new NullPointerException("keyType");
49          }
50          if (valueType == null) {
51              throw new NullPointerException("valueType");
52          }
53          this.keyType = keyType;
54          this.valueType = valueType;
55          getKeyEditor();
56          getValueEditor();
57          setTrimText(false);
58      }
59  
60      private PropertyEditor getKeyEditor() {
61          PropertyEditor e = PropertyEditorFactory.getInstance(keyType);
62          if (e == null) {
63              throw new IllegalArgumentException(
64                      "No key " + PropertyEditor.class.getSimpleName() + 
65                      " found for " + 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(
74                      "No value " + PropertyEditor.class.getSimpleName() + 
75                      " found for " + valueType.getSimpleName() + '.');
76          }
77          return e;
78      }
79  
80      @Override
81      @SuppressWarnings("unchecked")
82      protected final String toText(Object value) {
83          StringBuilder buf = new StringBuilder();
84          for (Object o: ((Map) value).entrySet()) {
85              Map.Entry entry = (Map.Entry) o;
86              Object ekey = entry.getKey();
87              Object evalue = entry.getValue();
88              
89              PropertyEditor ekeyEditor = PropertyEditorFactory.getInstance(ekey);
90              if (ekeyEditor == null) {
91                  throw new IllegalArgumentException(
92                          "No key " + PropertyEditor.class.getSimpleName() + 
93                          " found for " + ekey.getClass().getSimpleName() + '.');
94              }
95              ekeyEditor.setValue(ekey);
96              
97              PropertyEditor evalueEditor = PropertyEditorFactory.getInstance(evalue);
98              if (evalueEditor == null) {
99                  throw new IllegalArgumentException(
100                         "No value " + PropertyEditor.class.getSimpleName() + 
101                         " found for " + evalue.getClass().getSimpleName() + '.');
102             }
103             ekeyEditor.setValue(ekey);
104             evalueEditor.setValue(evalue);
105             
106             // TODO normalize.
107             String keyString = ekeyEditor.getAsText();
108             String valueString = evalueEditor.getAsText();
109             buf.append(keyString);
110             buf.append(" = ");
111             buf.append(valueString);
112             buf.append(", ");
113         }
114         
115         // Remove the last delimiter.
116         if (buf.length() >= 2) {
117             buf.setLength(buf.length() - 2);
118         }
119         return buf.toString();
120     }
121 
122     @Override
123     protected final Object toValue(String text) throws IllegalArgumentException {
124         PropertyEditor keyEditor = getKeyEditor();
125         PropertyEditor valueEditor = getValueEditor();
126         Map<Object, Object> answer = newMap();
127         Matcher m = ELEMENT.matcher(text);
128         TokenType lastTokenType = TokenType.ENTRY_DELIM;
129         Object key = null;
130         Object value = null;
131 
132         while (m.find()) {
133             if (m.group(1) != null) {
134                 switch (lastTokenType) {
135                 case VALUE: case ENTRY_DELIM:
136                     break;
137                 default:
138                     throw new IllegalArgumentException(
139                             "Unexpected entry delimiter: " + text);
140                 }
141                 
142                 lastTokenType = TokenType.ENTRY_DELIM;
143                 continue;
144             }
145             
146             if (m.group(2) != null) {
147                 if (lastTokenType != TokenType.KEY) {
148                     throw new IllegalArgumentException(
149                             "Unexpected key-value delimiter: " + text);
150                 }
151                 
152                 lastTokenType = TokenType.KEY_VALUE_DELIM;
153                 continue;
154             }
155             
156             
157             // TODO escape here.
158             String region = m.group();
159 
160             if (m.group(3) != null || m.group(4) != null) {
161                 // Skip the last '"'.
162                 m.region(m.end() + 1, m.regionEnd());
163             }
164             
165             switch (lastTokenType) {
166             case ENTRY_DELIM:
167                 keyEditor.setAsText(region);
168                 key = keyEditor.getValue();
169                 lastTokenType = TokenType.KEY;
170                 break;
171             case KEY_VALUE_DELIM:
172                 valueEditor.setAsText(region);
173                 value = valueEditor.getValue();
174                 lastTokenType = TokenType.VALUE;
175                 answer.put(key, value);
176                 break;
177             case KEY: case VALUE:
178                 throw new IllegalArgumentException(
179                         "Unexpected key or value: " + text);
180             }
181         }
182         
183         return answer;
184     }
185     
186     protected Map<Object, Object> newMap() {
187         return new LinkedHashMap<Object, Object>();
188     }
189     
190     private static enum TokenType {
191         ENTRY_DELIM,
192         KEY_VALUE_DELIM,
193         KEY,
194         VALUE,
195     }
196 }