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.text.MessageFormat;
24  import java.util.Collection;
25  import java.util.LinkedHashMap;
26  import java.util.Map;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  
30  /**
31   * A {@link PropertyEditor} which converts a {@link String} into
32   * a {@link Collection} and vice versa.
33   *
34   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
35   */
36  public class MapEditor extends AbstractPropertyEditor {
37      static final Pattern ELEMENT = Pattern.compile("([,\\s]+)|"
38              + // Entry delimiter
39              "(\\s*=\\s*)|"
40              + // Key-Value delimiter
41              "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|"
42              + "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)");
43  
44      private final Class<?> keyType;
45  
46      private final Class<?> valueType;
47      
48      private static final String NO_VALUE = "No value {1} found for {2}.";
49      private static final String NO_KEY = "No key {1} found for {2}.";
50  
51      /**
52       * Creates a new DateEditor instance
53       * 
54       * @param keyType The key type
55       * @param valueType The value type
56       */
57      public MapEditor(Class<?> keyType, Class<?> valueType) {
58          if (keyType == null) {
59              throw new IllegalArgumentException("keyType");
60          }
61          
62          if (valueType == null) {
63              throw new IllegalArgumentException("valueType");
64          }
65          
66          this.keyType = keyType;
67          this.valueType = valueType;
68          getKeyEditor();
69          getValueEditor();
70          setTrimText(false);
71      }
72  
73      private PropertyEditor getKeyEditor() {
74          PropertyEditor e = PropertyEditorFactory.getInstance(keyType);
75          
76          if (e == null) {
77              throw new IllegalArgumentException(MessageFormat.format(NO_KEY, PropertyEditor.class.getSimpleName(),
78                      keyType.getSimpleName()));
79          }
80          
81          return e;
82      }
83  
84      private PropertyEditor getValueEditor() {
85          PropertyEditor e = PropertyEditorFactory.getInstance(valueType);
86          
87          if (e == null) {
88              throw new IllegalArgumentException(MessageFormat.format(NO_VALUE, PropertyEditor.class.getSimpleName(),
89                      valueType.getSimpleName()));
90          }
91          
92          return e;
93      }
94  
95      @Override
96      protected final String toText(Object value) {
97          StringBuilder buf = new StringBuilder();
98          
99          for (Map.Entry<?,?> entry : ((Map<?,?>) value).entrySet()) {
100             Object ekey = entry.getKey();
101             Object evalue = entry.getValue();
102 
103             PropertyEditor ekeyEditor = PropertyEditorFactory.getInstance(ekey);
104             
105             if (ekeyEditor == null) {
106                 throw new IllegalArgumentException(MessageFormat.format(NO_KEY, PropertyEditor.class.getSimpleName(),
107                         ekey.getClass().getSimpleName()));
108             }
109             
110             ekeyEditor.setValue(ekey);
111 
112             PropertyEditor evalueEditor = PropertyEditorFactory.getInstance(evalue);
113             
114             if (evalueEditor == null) {
115                 throw new IllegalArgumentException(MessageFormat.format(NO_VALUE, PropertyEditor.class.getSimpleName(),
116                         evalue.getClass().getSimpleName()));
117             }
118             
119             ekeyEditor.setValue(ekey);
120             evalueEditor.setValue(evalue);
121 
122             // TODO normalize.
123             String keyString = ekeyEditor.getAsText();
124             String valueString = evalueEditor.getAsText();
125             buf.append(keyString);
126             buf.append(" = ");
127             buf.append(valueString);
128             buf.append(", ");
129         }
130 
131         // Remove the last delimiter.
132         if (buf.length() >= 2) {
133             buf.setLength(buf.length() - 2);
134         }
135         
136         return buf.toString();
137     }
138 
139     @Override
140     protected final Object toValue(String text) {
141         PropertyEditor keyEditor = getKeyEditor();
142         PropertyEditor valueEditor = getValueEditor();
143         Map<Object, Object> answer = newMap();
144         Matcher m = ELEMENT.matcher(text);
145         TokenType lastTokenType = TokenType.ENTRY_DELIM;
146         Object key = null;
147         Object value;
148 
149         while (m.find()) {
150             if (m.group(1) != null) {
151                 if ((lastTokenType != TokenType.VALUE) && (lastTokenType != TokenType.ENTRY_DELIM)) {
152                     throw new IllegalArgumentException("Unexpected entry delimiter: " + text);
153                 }
154 
155                 lastTokenType = TokenType.ENTRY_DELIM;
156                 continue;
157             }
158 
159             if (m.group(2) != null) {
160                 if (lastTokenType != TokenType.KEY) {
161                     throw new IllegalArgumentException("Unexpected key-value delimiter: " + text);
162                 }
163 
164                 lastTokenType = TokenType.KEY_VALUE_DELIM;
165                 continue;
166             }
167 
168             // TODO escape here.
169             String region = m.group();
170 
171             if (m.group(3) != null || m.group(4) != null) {
172                 // Skip the last '"'.
173                 m.region(m.end() + 1, m.regionEnd());
174             }
175 
176             switch (lastTokenType) {
177                 case ENTRY_DELIM:
178                     keyEditor.setAsText(region);
179                     key = keyEditor.getValue();
180                     lastTokenType = TokenType.KEY;
181                     break;
182                 case KEY_VALUE_DELIM:
183                     valueEditor.setAsText(region);
184                     value = valueEditor.getValue();
185                     lastTokenType = TokenType.VALUE;
186                     answer.put(key, value);
187                     break;
188                 case KEY:
189                 case VALUE:
190                     throw new IllegalArgumentException("Unexpected key or value: " + text);
191             }
192         }
193 
194         return answer;
195     }
196 
197     protected Map<Object, Object> newMap() {
198         return new LinkedHashMap<>();
199     }
200 
201     private enum TokenType {
202         ENTRY_DELIM, KEY_VALUE_DELIM, KEY, VALUE,
203     }
204 }