001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry.util.io;
016
017import java.util.Iterator;
018import java.util.List;
019
020import org.apache.hivemind.lib.util.StrategyRegistry;
021import org.apache.hivemind.lib.util.StrategyRegistryImpl;
022import org.apache.tapestry.Tapestry;
023import org.apache.tapestry.services.DataSqueezer;
024
025/**
026 * A class used to convert arbitrary objects to Strings and back. This has particular uses involving
027 * HTTP URLs and Cookies.
028 * 
029 * @author Howard Lewis Ship
030 */
031
032public class DataSqueezerImpl implements DataSqueezer
033{
034    private static final String NULL_PREFIX = "X";
035
036    private static final int ARRAY_SIZE = 90;
037
038    private static final int FIRST_ADAPTOR_OFFSET = 33;
039
040    /**
041     * An array of adaptors; this is used as a cheap lookup-table when unsqueezing. Each adaptor is
042     * identified by a single ASCII character, in the range of 33 ('!') to 122 (the letter 'z'). The
043     * offset into this table is the character minus 33.
044     */
045
046    private SqueezeAdaptor[] _adaptorByPrefix = new SqueezeAdaptor[ARRAY_SIZE];
047
048    /**
049     * AdaptorRegistry cache of adaptors.
050     */
051
052    private StrategyRegistry _adaptors = new StrategyRegistryImpl();
053
054    public void setSqueezeAdaptors(List adaptors)
055    {
056        Iterator i = adaptors.iterator();
057
058        while (i.hasNext())
059        {
060            SqueezeAdaptor adaptor = (SqueezeAdaptor) i.next();
061            register(adaptor);
062        }
063    }
064
065    /**
066     * Registers the adaptor with one or more single-character prefixes.
067     * <p>
068     * <b>Note</b>: This method should be used for testing purposes only! Squeeze adaptors are
069     * normally injected by HiveMind.
070     * 
071     * @param adaptor
072     *            the adaptor which to be registered.
073     */
074
075    public synchronized void register(SqueezeAdaptor adaptor)
076    {
077        if (adaptor == null)
078            throw new IllegalArgumentException(Tapestry.getMessage("DataSqueezer.null-adaptor"));
079
080        String prefix = adaptor.getPrefix();
081        int prefixLength = prefix.length();
082        int offset;
083
084        if (prefixLength < 1)
085            throw new IllegalArgumentException(Tapestry.getMessage("DataSqueezer.short-prefix"));
086
087        Class dataClass = adaptor.getDataClass();
088        if (dataClass == null)
089            throw new IllegalArgumentException(Tapestry.getMessage("DataSqueezer.null-class"));
090
091        for (int i = 0; i < prefixLength; i++)
092        {
093            char ch = prefix.charAt(i);
094
095            if (ch < '!' | ch > 'z')
096                throw new IllegalArgumentException(Tapestry
097                        .getMessage("DataSqueezer.prefix-out-of-range"));
098
099            offset = ch - FIRST_ADAPTOR_OFFSET;
100
101            if (_adaptorByPrefix[offset] != null)
102                throw new IllegalArgumentException(Tapestry.format(
103                        "DataSqueezer.adaptor-prefix-taken",
104                        prefix.substring(i, i)));
105
106            _adaptorByPrefix[offset] = adaptor;
107
108        }
109
110        _adaptors.register(dataClass, adaptor);
111    }
112
113    /**
114     * Squeezes the data object into a String by locating an appropriate adaptor that can perform
115     * the conversion. data may be null.
116     */
117
118    public String squeeze(Object data)
119    {
120        SqueezeAdaptor adaptor;
121
122        if (data == null)
123            return NULL_PREFIX;
124
125        adaptor = (SqueezeAdaptor) _adaptors.getStrategy(data.getClass());
126
127        return adaptor.squeeze(this, data);
128    }
129
130    /**
131     * A convience; invokes {@link #squeeze(Object)}for each element in the data array. If data is
132     * null, returns null.
133     */
134
135    public String[] squeeze(Object[] data)
136    {
137        if (data == null)
138            return null;
139
140        int length = data.length;
141        String[] result;
142
143        result = new String[length];
144
145        for (int i = 0; i < length; i++)
146            result[i] = squeeze(data[i]);
147
148        return result;
149    }
150
151    /**
152     * Unsqueezes the string. Note that in a special case, where the first character of the string
153     * is not a recognized prefix, it is assumed that the string is simply a string, and return with
154     * no change.
155     */
156
157    public Object unsqueeze(String string)
158    {
159        SqueezeAdaptor adaptor = null;
160
161        if (string.equals(NULL_PREFIX))
162            return null;
163
164        int offset = string.charAt(0) - FIRST_ADAPTOR_OFFSET;
165
166        if (offset >= 0 && offset < _adaptorByPrefix.length)
167            adaptor = _adaptorByPrefix[offset];
168
169        // If the adaptor is not otherwise recognized, the it is simply
170        // an encoded String (the StringAdaptor may not have added
171        // a prefix).
172
173        if (adaptor == null)
174            return string;
175
176        // Adaptor should never be null, because we always supply
177        // an adaptor for String
178
179        return adaptor.unsqueeze(this, string);
180    }
181
182    /**
183     * Convienience method for unsqueezing many strings (back into objects).
184     * <p>
185     * If strings is null, returns null.
186     */
187
188    public Object[] unsqueeze(String[] strings)
189    {
190        if (strings == null)
191            return null;
192
193        int length = strings.length;
194        Object[] result;
195
196        result = new Object[length];
197
198        for (int i = 0; i < length; i++)
199            result[i] = unsqueeze(strings[i]);
200
201        return result;
202    }
203
204    public String toString()
205    {
206        StringBuffer buffer;
207
208        buffer = new StringBuffer();
209        buffer.append("DataSqueezer[adaptors=<");
210        buffer.append(_adaptors.toString());
211        buffer.append(">]");
212
213        return buffer.toString();
214    }
215}