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 */ 020package org.apache.mina.integration.beans; 021 022import java.beans.PropertyEditor; 023import java.util.Collection; 024import java.util.LinkedHashMap; 025import java.util.Map; 026import java.util.regex.Matcher; 027import 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 <a href="http://mina.apache.org">Apache MINA Project</a> 034 */ 035public class MapEditor extends AbstractPropertyEditor { 036 static final Pattern ELEMENT = Pattern.compile("([,\\s]+)|" 037 + // Entry delimiter 038 "(\\s*=\\s*)|" 039 + // Key-Value delimiter 040 "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|" 041 + "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)"); 042 043 private final Class<?> keyType; 044 045 private final Class<?> valueType; 046 047 public MapEditor(Class<?> keyType, Class<?> valueType) { 048 if (keyType == null) { 049 throw new IllegalArgumentException("keyType"); 050 } 051 if (valueType == null) { 052 throw new IllegalArgumentException("valueType"); 053 } 054 this.keyType = keyType; 055 this.valueType = valueType; 056 getKeyEditor(); 057 getValueEditor(); 058 setTrimText(false); 059 } 060 061 private PropertyEditor getKeyEditor() { 062 PropertyEditor e = PropertyEditorFactory.getInstance(keyType); 063 if (e == null) { 064 throw new IllegalArgumentException("No key " + PropertyEditor.class.getSimpleName() + " found for " 065 + keyType.getSimpleName() + '.'); 066 } 067 return e; 068 } 069 070 private PropertyEditor getValueEditor() { 071 PropertyEditor e = PropertyEditorFactory.getInstance(valueType); 072 if (e == null) { 073 throw new IllegalArgumentException("No value " + PropertyEditor.class.getSimpleName() + " found for " 074 + valueType.getSimpleName() + '.'); 075 } 076 return e; 077 } 078 079 @Override 080 protected final String toText(Object value) { 081 StringBuilder buf = new StringBuilder(); 082 083 for (Object o : ((Map<?,?>) value).entrySet()) { 084 Map.Entry<?,?> entry = (Map.Entry<?,?>) o; 085 Object ekey = entry.getKey(); 086 Object evalue = entry.getValue(); 087 088 PropertyEditor ekeyEditor = PropertyEditorFactory.getInstance(ekey); 089 if (ekeyEditor == null) { 090 throw new IllegalArgumentException("No key " + PropertyEditor.class.getSimpleName() + " found for " 091 + ekey.getClass().getSimpleName() + '.'); 092 } 093 ekeyEditor.setValue(ekey); 094 095 PropertyEditor evalueEditor = PropertyEditorFactory.getInstance(evalue); 096 if (evalueEditor == null) { 097 throw new IllegalArgumentException("No value " + PropertyEditor.class.getSimpleName() + " found for " 098 + evalue.getClass().getSimpleName() + '.'); 099 } 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}