Clover coverage report - Code Coverage for tapestry release 4.0-beta-12
Coverage timestamp: Sun Oct 30 2005 16:22:01 EST
file stats: LOC: 196   Methods: 5
NCLOC: 113   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
PersistentPropertyDataEncoderImpl.java 100% 100% 100% 100%
coverage
 1    // Copyright 2005 The Apache Software Foundation
 2    //
 3    // Licensed under the Apache License, Version 2.0 (the "License");
 4    // you may not use this file except in compliance with the License.
 5    // You may obtain a copy of the License at
 6    //
 7    // http://www.apache.org/licenses/LICENSE-2.0
 8    //
 9    // Unless required by applicable law or agreed to in writing, software
 10    // distributed under the License is distributed on an "AS IS" BASIS,
 11    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    // See the License for the specific language governing permissions and
 13    // limitations under the License.
 14   
 15    package org.apache.tapestry.record;
 16   
 17    import java.io.BufferedInputStream;
 18    import java.io.BufferedOutputStream;
 19    import java.io.ByteArrayInputStream;
 20    import java.io.ByteArrayOutputStream;
 21    import java.io.IOException;
 22    import java.io.InputStream;
 23    import java.io.ObjectInputStream;
 24    import java.io.ObjectOutputStream;
 25    import java.util.ArrayList;
 26    import java.util.Collections;
 27    import java.util.Iterator;
 28    import java.util.List;
 29    import java.util.zip.GZIPInputStream;
 30    import java.util.zip.GZIPOutputStream;
 31   
 32    import org.apache.commons.codec.binary.Base64;
 33    import org.apache.hivemind.ApplicationRuntimeException;
 34    import org.apache.hivemind.ClassResolver;
 35    import org.apache.hivemind.HiveMind;
 36    import org.apache.hivemind.util.Defense;
 37    import org.apache.tapestry.util.io.ResolvingObjectInputStream;
 38    import org.apache.tapestry.util.io.TeeOutputStream;
 39   
 40    /**
 41    * Responsible for converting lists of {@link org.apache.tapestry.record.PropertyChange}s back and
 42    * forth to a URL safe encoded string.
 43    * <p>
 44    * A possible improvement would be to encode the binary data with encryption both on and off, and
 45    * select the shortest (prefixing with a character that identifies whether encryption should be used
 46    * to decode).
 47    *
 48    * @author Howard M. Lewis Ship
 49    * @since 4.0
 50    */
 51    public class PersistentPropertyDataEncoderImpl implements PersistentPropertyDataEncoder
 52    {
 53    private ClassResolver _classResolver;
 54   
 55    /**
 56    * Prefix on the MIME encoding that indicates that the encoded data is not encoded.
 57    */
 58   
 59    public static final String BYTESTREAM_PREFIX = "B";
 60   
 61    /**
 62    * Prefix on the MIME encoding that indicates that the encoded data is encoded with GZIP.
 63    */
 64   
 65    public static final String GZIP_BYTESTREAM_PREFIX = "Z";
 66   
 67  5 public String encodePageChanges(List changes)
 68    {
 69  5 Defense.notNull(changes, "changes");
 70   
 71  5 if (changes.isEmpty())
 72  1 return "";
 73   
 74  4 try
 75    {
 76  4 ByteArrayOutputStream bosPlain = new ByteArrayOutputStream();
 77  4 ByteArrayOutputStream bosCompressed = new ByteArrayOutputStream();
 78   
 79  4 GZIPOutputStream gos = new GZIPOutputStream(bosCompressed);
 80   
 81  4 TeeOutputStream tos = new TeeOutputStream(bosPlain, gos);
 82   
 83  4 ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(tos));
 84   
 85  4 writeChangesToStream(changes, oos);
 86   
 87  3 oos.close();
 88   
 89  3 boolean useCompressed = bosCompressed.size() < bosPlain.size();
 90   
 91  3 byte[] data = useCompressed ? bosCompressed.toByteArray() : bosPlain.toByteArray();
 92   
 93  3 byte[] encoded = Base64.encodeBase64(data);
 94   
 95  3 String prefix = useCompressed ? GZIP_BYTESTREAM_PREFIX : BYTESTREAM_PREFIX;
 96   
 97  3 return prefix + new String(encoded);
 98    }
 99    catch (Exception ex)
 100    {
 101  1 throw new ApplicationRuntimeException(RecordMessages.encodeFailure(ex), ex);
 102    }
 103    }
 104   
 105  6 public List decodePageChanges(String encoded)
 106    {
 107  6 if (HiveMind.isBlank(encoded))
 108  1 return Collections.EMPTY_LIST;
 109   
 110  5 String prefix = encoded.substring(0, 1);
 111   
 112  5 if (!(prefix.equals(BYTESTREAM_PREFIX) || prefix.equals(GZIP_BYTESTREAM_PREFIX)))
 113  1 throw new ApplicationRuntimeException(RecordMessages.unknownPrefix(prefix));
 114   
 115  4 try
 116    {
 117    // Strip off the prefix, feed that in as a MIME stream.
 118   
 119  4 byte[] decoded = Base64.decodeBase64(encoded.substring(1).getBytes());
 120   
 121  4 InputStream is = new ByteArrayInputStream(decoded);
 122   
 123  4 if (prefix.equals(GZIP_BYTESTREAM_PREFIX))
 124  3 is = new GZIPInputStream(is);
 125   
 126    // I believe this is more efficient; the buffered input stream should ask the
 127    // GZIP stream for large blocks of un-gzipped bytes, with should be more efficient.
 128    // The object input stream will probably be looking for just a few bytes at
 129    // a time. We use a resolving object input stream that knows how to find
 130    // classes not normally acessible.
 131   
 132  3 ObjectInputStream ois = new ResolvingObjectInputStream(_classResolver,
 133    new BufferedInputStream(is));
 134   
 135  3 List result = readChangesFromStream(ois);
 136   
 137  3 ois.close();
 138   
 139  3 return result;
 140    }
 141    catch (Exception ex)
 142    {
 143  1 throw new ApplicationRuntimeException(RecordMessages.decodeFailure(ex), ex);
 144    }
 145    }
 146   
 147  4 private void writeChangesToStream(List changes, ObjectOutputStream oos) throws IOException
 148    {
 149  4 oos.writeInt(changes.size());
 150   
 151  4 Iterator i = changes.iterator();
 152  4 while (i.hasNext())
 153    {
 154  23 PropertyChange pc = (PropertyChange) i.next();
 155   
 156  23 String componentPath = pc.getComponentPath();
 157  23 String propertyName = pc.getPropertyName();
 158  23 Object value = pc.getNewValue();
 159   
 160  23 oos.writeBoolean(componentPath != null);
 161   
 162  23 if (componentPath != null)
 163  11 oos.writeUTF(componentPath);
 164   
 165  23 oos.writeUTF(propertyName);
 166  23 oos.writeObject(value);
 167    }
 168    }
 169   
 170  3 private List readChangesFromStream(ObjectInputStream ois) throws IOException,
 171    ClassNotFoundException
 172    {
 173  3 List result = new ArrayList();
 174   
 175  3 int count = ois.readInt();
 176   
 177  3 for (int i = 0; i < count; i++)
 178    {
 179  22 boolean hasPath = ois.readBoolean();
 180  22 String componentPath = hasPath ? ois.readUTF() : null;
 181  22 String propertyName = ois.readUTF();
 182  22 Object value = ois.readObject();
 183   
 184  22 PropertyChangeImpl pc = new PropertyChangeImpl(componentPath, propertyName, value);
 185   
 186  22 result.add(pc);
 187    }
 188   
 189  3 return result;
 190    }
 191   
 192  11 public void setClassResolver(ClassResolver resolver)
 193    {
 194  11 _classResolver = resolver;
 195    }
 196    }