| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| Encoder |
|
| 5.555555555555555;5.556 | ||||
| Encoder$Block |
|
| 5.555555555555555;5.556 |
| 1 | /* | |
| 2 | * Copyright (C) 1998-2000 Semiotek Inc. All Rights Reserved. | |
| 3 | * | |
| 4 | * Redistribution and use in source and binary forms, with or without | |
| 5 | * modification, are permitted under the terms of either of the following | |
| 6 | * Open Source licenses: | |
| 7 | * | |
| 8 | * The GNU General Public License, version 2, or any later version, as | |
| 9 | * published by the Free Software Foundation | |
| 10 | * (http://www.fsf.org/copyleft/gpl.html); | |
| 11 | * | |
| 12 | * or | |
| 13 | * | |
| 14 | * The Semiotek Public License (http://webmacro.org/LICENSE.) | |
| 15 | * | |
| 16 | * This software is provided "as is", with NO WARRANTY, not even the | |
| 17 | * implied warranties of fitness to purpose, or merchantability. You | |
| 18 | * assume all risks and liabilities associated with its use. | |
| 19 | * | |
| 20 | * See www.webmacro.org for more information on the WebMacro project. | |
| 21 | */ | |
| 22 | ||
| 23 | package org.webmacro.util; | |
| 24 | ||
| 25 | import org.slf4j.Logger; | |
| 26 | import org.slf4j.LoggerFactory; | |
| 27 | import org.webmacro.Broker; | |
| 28 | import org.webmacro.InitException; | |
| 29 | import org.webmacro.ResourceException; | |
| 30 | import org.webmacro.resource.CacheElement; | |
| 31 | import org.webmacro.resource.CacheManager; | |
| 32 | import org.webmacro.resource.ResourceLoader; | |
| 33 | import org.webmacro.resource.TrivialCacheManager; | |
| 34 | ||
| 35 | import java.io.UnsupportedEncodingException; | |
| 36 | ||
| 37 | /** | |
| 38 | * An encoder is used to encode strings into a particular encoding in | |
| 39 | * preparation for sending the data as part of a response. An encoder is | |
| 40 | * constructed with a particular output encoding and is then expected to | |
| 41 | * properly encode strings with that output encoding for its lifetime. | |
| 42 | * | |
| 43 | * <p><code>Encoder</code> instances are obtained via the | |
| 44 | * <code>EncoderProvider</code> implementation that is configured at | |
| 45 | * initialization time. The encoder provider is responsible communicating | |
| 46 | * the encoding scheme to be used by the encoder at construct time. | |
| 47 | * | |
| 48 | * <p> The mechanism is factored into the encoder interface to allow for | |
| 49 | * caching encoders with differing caching schemes based on the server | |
| 50 | * environment's requirements or no caching scheme at all. | |
| 51 | * | |
| 52 | * @see EncoderProvider | |
| 53 | * @since 0.96 | |
| 54 | * @author Michael Bayne | |
| 55 | */ | |
| 56 | ||
| 57 | public class Encoder implements ResourceLoader | |
| 58 | { | |
| 59 | ||
| 60 | private String _encoding; | |
| 61 | private CacheManager _cache; | |
| 62 | 2 | static Logger _log = LoggerFactory.getLogger(Encoder.class); |
| 63 | ||
| 64 | /** | |
| 65 | * Creates an encoder instance with the supplied encoding. | |
| 66 | * | |
| 67 | * @exception UnsupportedEncodingException Thrown when the underlying | |
| 68 | * Java encoding mechanism does not provide support for the requesting | |
| 69 | * encoding. | |
| 70 | */ | |
| 71 | public Encoder (String encoding) | |
| 72 | throws UnsupportedEncodingException | |
| 73 | 12 | { |
| 74 | // enforce some specific rules related to choice of encodings | |
| 75 | 12 | if (encoding == null || |
| 76 | encoding.equalsIgnoreCase("UNICODE") || | |
| 77 | encoding.equalsIgnoreCase("UNICODEBIG") || | |
| 78 | encoding.equalsIgnoreCase("UNICODELITTLE") || | |
| 79 | encoding.equalsIgnoreCase("UTF16")) | |
| 80 | { | |
| 81 | 0 | String err = "The encoding you specified is invalid: " + |
| 82 | encoding + ". Note that the UNICODE and UTF16 encodings " + | |
| 83 | "are not supported by WebMacro because they prefix the " + | |
| 84 | "stream with a marker indicating whether the stream is " + | |
| 85 | "big endian or little endian. Instead choose the byte " + | |
| 86 | "ordering yourself by using the UTF-16BE or UTF-16LE " + | |
| 87 | "encodings."; | |
| 88 | 0 | throw new UnsupportedEncodingException(err); |
| 89 | } | |
| 90 | ||
| 91 | // Check to be sure that this encoding is supported. this will | |
| 92 | // throw an UnsupportedEncodingException if the JVM doesn't | |
| 93 | // support the requested encoding | |
| 94 | 12 | "some test string".getBytes(encoding); |
| 95 | ||
| 96 | // keep track of this for later | |
| 97 | 12 | _encoding = encoding; |
| 98 | 12 | } |
| 99 | ||
| 100 | public void init (Broker b, Settings config) | |
| 101 | throws InitException | |
| 102 | { | |
| 103 | String cacheManager; | |
| 104 | ||
| 105 | 12 | cacheManager = b.getSetting("Encoder." + _encoding + ".CacheManager"); |
| 106 | 12 | if (cacheManager == null) |
| 107 | 12 | cacheManager = b.getSetting("Encoder.*.CacheManager"); |
| 108 | 12 | if (cacheManager == null || cacheManager.equals("")) |
| 109 | { | |
| 110 | 0 | _log.info("No cache manager specified for encoding " + _encoding |
| 111 | + ", using TrivialCacheManager"); | |
| 112 | 0 | _cache = new TrivialCacheManager(); |
| 113 | } | |
| 114 | else | |
| 115 | { | |
| 116 | try | |
| 117 | { | |
| 118 | 12 | Class c = b.classForName(cacheManager); |
| 119 | 12 | _cache = (CacheManager) c.newInstance(); |
| 120 | } | |
| 121 | 0 | catch (Exception e) |
| 122 | { | |
| 123 | 0 | _log.warn("Unable to load cache manager " + cacheManager |
| 124 | + " for encoding type " + _encoding | |
| 125 | + ", using TrivialCacheManager. Reason:\n" + e); | |
| 126 | 0 | _cache = new TrivialCacheManager(); |
| 127 | 12 | } |
| 128 | } | |
| 129 | 12 | _cache.init(b, config, _encoding); |
| 130 | 12 | } |
| 131 | ||
| 132 | /** | |
| 133 | * Load an object from permanent storage (or construct it) on | |
| 134 | * demand. | |
| 135 | */ | |
| 136 | public Object load (Object query, CacheElement ce) | |
| 137 | throws ResourceException | |
| 138 | { | |
| 139 | try | |
| 140 | { | |
| 141 | 532 | if (query instanceof Block) |
| 142 | { | |
| 143 | 532 | String[] source = ((Block) query).text; |
| 144 | 532 | byte[][] encoded = new byte[source.length][]; |
| 145 | 2786 | for (int i = 0; i < source.length; i++) |
| 146 | { | |
| 147 | 2254 | encoded[i] = source[i].getBytes(_encoding); |
| 148 | } | |
| 149 | 532 | return encoded; |
| 150 | ||
| 151 | } | |
| 152 | 0 | else if (query instanceof String) |
| 153 | { | |
| 154 | 0 | return ((String) query).getBytes(_encoding); |
| 155 | } | |
| 156 | else | |
| 157 | { | |
| 158 | 0 | return query.toString().getBytes(_encoding); |
| 159 | } | |
| 160 | ||
| 161 | } | |
| 162 | 0 | catch (UnsupportedEncodingException uee) |
| 163 | { | |
| 164 | // this should never happen as we check in the constructor to | |
| 165 | // ensure that the encoding is supported | |
| 166 | 0 | throw new ResourceException("Unable to encode: " + uee); |
| 167 | } | |
| 168 | } | |
| 169 | ||
| 170 | public Object load (String query, CacheElement ce) | |
| 171 | throws ResourceException | |
| 172 | { | |
| 173 | try | |
| 174 | { | |
| 175 | 0 | return query.getBytes(_encoding); |
| 176 | } | |
| 177 | 0 | catch (UnsupportedEncodingException uee) |
| 178 | { | |
| 179 | // this should never happen as we check in the constructor to | |
| 180 | // ensure that the encoding is supported | |
| 181 | 0 | throw new ResourceException("Unable to encode: " + uee); |
| 182 | } | |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * Encodes the supplied string using the encoding bound to this encoder | |
| 187 | * at construct time. | |
| 188 | * | |
| 189 | * @return The encoded version of the supplied string. | |
| 190 | * | |
| 191 | * @exception UnsupportedEncodingException Thrown when the underlying | |
| 192 | * Java encoding mechanism does not provide support for the encoding | |
| 193 | * used by this encoder instance. | |
| 194 | */ | |
| 195 | public final byte[] encode (String source) | |
| 196 | throws UnsupportedEncodingException | |
| 197 | { | |
| 198 | try | |
| 199 | { | |
| 200 | 0 | return (byte[]) _cache.get(source, this); |
| 201 | } | |
| 202 | 0 | catch (ResourceException e) |
| 203 | { | |
| 204 | 0 | throw new UnsupportedEncodingException("Encoder: Could not encode; " |
| 205 | + e); | |
| 206 | } | |
| 207 | } | |
| 208 | ||
| 209 | /** | |
| 210 | * Encodes the supplied block of strings using the encoding bound to | |
| 211 | * this encoder at construct time. | |
| 212 | * | |
| 213 | * @return The encoded version of the supplied block of strings. | |
| 214 | * | |
| 215 | * @exception UnsupportedEncodingException Thrown when the underlying | |
| 216 | * Java encoding mechanism does not provide support for the encoding | |
| 217 | * used by this encoder instance. | |
| 218 | */ | |
| 219 | public final byte[][] encode (Block source) | |
| 220 | throws UnsupportedEncodingException | |
| 221 | { | |
| 222 | try | |
| 223 | { | |
| 224 | 56066 | return (byte[][]) _cache.get(source, this); |
| 225 | } | |
| 226 | 0 | catch (ResourceException e) |
| 227 | { | |
| 228 | 0 | throw new UnsupportedEncodingException("Encoder: Could not encode; " |
| 229 | + e); | |
| 230 | } | |
| 231 | } | |
| 232 | ||
| 233 | /** | |
| 234 | * The block class provides a means by which encoder users can encode | |
| 235 | * entire blocks of text at once (and have those encoded blocks | |
| 236 | * cached). | |
| 237 | */ | |
| 238 | public static class Block | |
| 239 | { | |
| 240 | ||
| 241 | public String[] text; | |
| 242 | ||
| 243 | public Block (String[] text) | |
| 244 | 1484 | { |
| 245 | 1484 | this.text = text; |
| 246 | ||
| 247 | // we compute the combined hash of our string array so that we | |
| 248 | // can behave as an efficient, stable key while allowing our | |
| 249 | // strings to maintain happy independent existences | |
| 250 | 1484 | long strhash = 0; |
| 251 | 6750 | for (int i = 0; i < text.length; i++) |
| 252 | { | |
| 253 | 5266 | strhash = (strhash + (long) text[i].hashCode()) % |
| 254 | Integer.MAX_VALUE; | |
| 255 | } | |
| 256 | 1484 | _hashCode = (int) strhash; |
| 257 | 1484 | } |
| 258 | ||
| 259 | public int hashCode () | |
| 260 | { | |
| 261 | 56598 | return _hashCode; |
| 262 | } | |
| 263 | ||
| 264 | public boolean equals (Object other) | |
| 265 | { | |
| 266 | // we try to be as efficient as possible about this, but to be | |
| 267 | // correct, we have to compare every string | |
| 268 | 69930 | if (!(other instanceof Block)) |
| 269 | { | |
| 270 | 0 | return false; |
| 271 | } | |
| 272 | ||
| 273 | 69930 | Block ob = (Block) other; |
| 274 | ||
| 275 | // check the obvious things | |
| 276 | 69930 | if (this == ob || text == ob.text) |
| 277 | { | |
| 278 | 10750 | return true; |
| 279 | } | |
| 280 | 59180 | if (text == null || ob.text == null) |
| 281 | { | |
| 282 | 0 | return false; |
| 283 | } | |
| 284 | 59180 | if (ob.text.length != text.length) |
| 285 | { | |
| 286 | 14396 | return false; |
| 287 | } | |
| 288 | ||
| 289 | // compare each string individually | |
| 290 | 170392 | for (int i = 0; i < text.length; i++) |
| 291 | { | |
| 292 | 125608 | if (!text[i].equals(ob.text[i])) |
| 293 | { | |
| 294 | 0 | return false; |
| 295 | } | |
| 296 | } | |
| 297 | ||
| 298 | 44784 | return true; |
| 299 | } | |
| 300 | ||
| 301 | protected int _hashCode; | |
| 302 | } | |
| 303 | } |