001 /*
002 * Copyright 2004 The Apache Software Foundation
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.apache.commons.convert;
017
018 import java.util.HashMap;
019 import java.util.Map;
020
021 import org.apache.commons.convert.conversion.ClassToStringConversion;
022 import org.apache.commons.convert.conversion.ObjectToStringConversionFactory;
023 import org.apache.commons.convert.conversion.TimeZoneToStringConversionFactory;
024
025 /**
026 * ConversionRegistry manages the <code>Conversion</code> and
027 * <code>ConversionFactory</code> objects.
028 * <p>
029 * This class allows conversions to be added, removed and looked up.
030 * This implementation is fully synchronized.
031 * <pre>
032 * Converter converter = new Converter();
033 * converter.getRegistry().addDefaultConversions();
034 * converter.getRegistry().addConversion(new MyNewConversion());
035 * </pre>
036 *
037 * @author Stephen Colebourne
038 * @version $Id: ConversionRegistry.java 155441 2005-02-26 13:19:22Z dirkv $
039 * @since 1.0
040 */
041 public class ConversionRegistry {
042
043 /** Map of from class to conversion */
044 protected Map iConversions = new HashMap();
045 /** Array of conversion factories */
046 protected ConversionFactory[] iFactories = new ConversionFactory[0];
047 /** Object to synchronize on for factories */
048 protected final Object iFactoryLock = new Object();
049
050 /**
051 * Restricted constructor, use Converter.
052 */
053 protected ConversionRegistry() {
054 super();
055 }
056
057 //-----------------------------------------------------------------------
058 /**
059 * Add the default set of conversions to the registry.
060 */
061 public void addDefaultConversions() {
062 synchronized (iConversions) {
063 addConversion(ClassToStringConversion.INSTANCE);
064 }
065 synchronized (iFactoryLock) {
066 addConversionFactory(ObjectToStringConversionFactory.INSTANCE);
067 addConversionFactory(TimeZoneToStringConversionFactory.INSTANCE);
068 }
069 }
070
071 //-----------------------------------------------------------------------
072 /**
073 * Adds a Conversion to the map of known conversions.
074 * Any previous conversion for this from-to pair is replaced.
075 *
076 * @param conv the conversion to add
077 */
078 public void addConversion(Conversion conv) {
079 if (conv != null) {
080 synchronized (iConversions) {
081 Map map = (Map) iConversions.get(conv.getFromType());
082 if (map == null) {
083 map = new HashMap();
084 iConversions.put(conv.getFromType(), map);
085 }
086 map.put(conv.getToType(), conv);
087 }
088 }
089 }
090
091 /**
092 * Adds a ConversionFactory to the set of known factories.
093 * Any previous factory that matches by <code>equals()</code> is replaced.
094 *
095 * @param factory the factory to add
096 */
097 public void addConversionFactory(ConversionFactory factory) {
098 if (factory != null) {
099 synchronized (iFactoryLock) {
100 ConversionFactory[] oldFactories = iFactories;
101 for (int i = 0; i < oldFactories.length; i++) {
102 if (oldFactories[i].equals(factory)) {
103 iFactories[i] = factory;
104 return;
105 }
106 }
107 ConversionFactory[] newFactories = new ConversionFactory[oldFactories.length + 1];
108 System.arraycopy(oldFactories, 0, newFactories, 0, oldFactories.length);
109 newFactories[oldFactories.length] = factory;
110 iFactories = newFactories;
111 }
112 }
113 }
114
115 //-----------------------------------------------------------------------
116 /**
117 * Gets the conversion object that best matches the from and to types.
118 * <p>
119 * The lookup first examines the known conversions. If none is found, the
120 * factories are used to search for and create a conversion. If no suitable
121 * factory is found then <code>null</code> is returned.
122 *
123 * @param value the value that will be converted, read only and not to be stored
124 * @param fromType the type to convert from
125 * @param toType the type to convert to
126 * @return the best matching conversion, null if no match
127 */
128 public Conversion getConversion(Object value, Class fromType, Class toType) {
129 // try known conversions
130 synchronized (iConversions) {
131 Map map = (Map) iConversions.get(fromType);
132 if (map != null) {
133 Conversion conv = (Conversion) map.get(toType);
134 if (conv != null) {
135 return conv;
136 }
137 }
138 }
139
140 // try factories
141 int max = 0;
142 ConversionFactory maxFactory = null;
143 synchronized (iFactoryLock) {
144 ConversionFactory[] factories = iFactories;
145 for (int i = 0; i < factories.length; i++) {
146 int match = factories[i].getMatchPercent(value, fromType, toType);
147 if (match > max) {
148 max = match;
149 maxFactory = factories[i];
150 }
151 }
152 }
153 if (maxFactory != null) {
154 Conversion conv = maxFactory.getInstance(value, fromType, toType);
155 addConversion(conv);
156 return conv;
157 }
158
159 // no match
160 return null;
161 }
162
163 //-----------------------------------------------------------------------
164 /**
165 * Returns a string describing this object.
166 *
167 * @return a string describing this object
168 */
169 public String toString() {
170 return "ConverterRegistry";
171 }
172
173 }