/**
 *     Copyright 2011 Membase, Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package org.couchbase.mock.memcached;

import java.util.Date;

/**
 * @author Trond Norbye
 */
public class Item {
    private final KeySpec keySpec;
    private final int flags;
    private int expiryTime;
    private byte[] value;
    private long cas;
    private long modificationTime;

    /** When the lock expires, if any */
    private int lockExpiryTime;

    public Item(KeySpec ks, int flags, int expiryTime, byte[] value, long cas) {
        this.keySpec = ks;
        this.flags = flags;
        this.value = value;
        this.cas = cas;
        this.expiryTime = VBucketStore.convertExpiryTime(expiryTime);
    }

    public Item(KeySpec ks) {
        this.keySpec = ks;
        this.flags = -1;
        this.expiryTime = -1;
        this.value = null;
        this.cas = -1;
        this.modificationTime = -1;
    }

    /**
     * Copy constructor
     * @param src Item whose contents we should copy
     */
    public Item(Item src) {
        this.keySpec = src.keySpec;
        this.flags = src.flags;
        this.expiryTime = src.expiryTime;
        this.value = src.value;
        this.cas = src.cas;
        this.modificationTime = src.modificationTime;
        this.lockExpiryTime = src.lockExpiryTime;
    }

    public int getExpiryTime() {
        return expiryTime;
    }

    public long getExpiryTimeInMillis() {
        return (long) expiryTime * 1000L;
    }

    public void setExpiryTime(int e) {
        expiryTime = VBucketStore.convertExpiryTime(e);
    }

    public long getModificationTime() {
        return modificationTime;
    }

    public int getFlags() {
        return flags;
    }

    public KeySpec getKeySpec() {
        return this.keySpec;
    }

    public byte[] getValue() {
        return value;
    }

    public long getCas() {
        return cas;
    }

    void setCas(long l) {
        modificationTime = new Date().getTime();
        cas = l;
    }

    void setLockExpiryTime(int e) {
        lockExpiryTime = VBucketStore.convertExpiryTime(e);
    }

    int getLockExpiryTime() {
        return lockExpiryTime;
    }

    public boolean isLocked() {
        if (lockExpiryTime == 0) {
            return false;
        }

        long now = new Date().getTime() / 1000;
        return now <= lockExpiryTime;
    }

    /**
     * Given a cas, ensure that the item is unlocked.
     * Will succeed if cas matches the existing cas, or if the item is not
     * locked
     * @param cas the cas value used to perform the operation
     * @return true if item is *not* locked.
     */
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public boolean ensureUnlocked(long cas)
    {
        if (cas == this.cas) {
            lockExpiryTime = 0;
            return true;
        } else {
            return !isLocked();
        }
    }

    public void append(Item i) {
        byte[] s1 = value;
        byte[] s2 = i.getValue();
        byte[] dst = new byte[s1.length + s2.length];

        System.arraycopy(s1, 0, dst, 0, s1.length);
        System.arraycopy(s2, 0, dst, s1.length, s2.length);
        value = dst;
    }

    public void prepend(Item i) {
        byte[] s1 = value;
        byte[] s2 = i.getValue();
        byte[] dst = new byte[s1.length + s2.length];

        System.arraycopy(s2, 0, dst, 0, s2.length);
        System.arraycopy(s1, 0, dst, s2.length, s1.length);
        value = dst;
    }

}

@@ -653,16 +653,243 @@
 ached;%0A%0A
+import org.couchbase.mock.util.Base64;%0Aimport sun.nio.cs.StandardCharsets;%0A%0Aimport java.nio.ByteBuffer;%0Aimport java.nio.charset.CharacterCodingException;%0Aimport java.nio.charset.Charset;%0Aimport java.nio.charset.CharsetDecoder;%0A
 import j
@@ -1090,51 +1090,202 @@
 ate 
-long cas;%0A    private long modificationTime
+String cached_UTF8 = null;%0A    private String cached_B64 = null;%0A    private long cas;%0A    private long modificationTime;%0A    final static private Charset UTF8_CHARSET = Charset.forName(%22UTF-8%22)
 ;%0A%0A 
@@ -2214,32 +2214,118 @@
 lockExpiryTime;%0A
+        this.cached_B64 = src.cached_B64;%0A        this.cached_UTF8 = src.cached_UTF8;%0A
     %7D%0A%0A    publi
@@ -2834,32 +2834,541 @@
 n value;%0A    %7D%0A%0A
+    public String getUtf8() throws CharacterCodingException %7B%0A        if (cached_UTF8 != null) %7B%0A            return cached_UTF8;%0A        %7D%0A%0A        CharsetDecoder dec = UTF8_CHARSET.newDecoder();%0A        cached_UTF8 = dec.decode(ByteBuffer.wrap(value)).toString();%0A        return cached_UTF8;%0A    %7D%0A%0A    public String getBase64() %7B%0A        if (cached_B64 != null) %7B%0A            return cached_B64;%0A        %7D else %7B%0A            cached_B64 = Base64.encode(value);%0A            return cached_B64;%0A        %7D%0A    %7D%0A%0A
     public long 

Add UTF8/b64 item wrappers