packages feed

hblock-0.1.0.0: Data/Indexation/UUID.hs

{-# LANGUAGE OverloadedStrings, TemplateHaskell, DeriveDataTypeable, TypeFamilies, GeneralizedNewtypeDeriving #-}
module Data.Indexation.UUID where

import Data.UUID (fromString)
import Data.UUID.V4 (nextRandom)
import qualified Data.UUID as U 
import Data.SafeCopy
import Data.Text (unpack, Text)
import qualified Data.Text as T
import Control.Applicative ((<$>))
import Web.PathPieces
import Data.Aeson
import qualified Data.Aeson as Aeson
import Text.Blaze(ToMarkup(..))
import Data.Bits (shiftR, (.&.), (.|.), shiftL)
import Control.Monad (mzero)
import Data.Char (intToDigit, isHexDigit, digitToInt)
import Data.Word(Word32)
-- Imports for deriving
import Data.Data
import Data.Hashable
import Control.DeepSeq (NFData(..))
import Foreign.Storable
import Data.Serialize
import Data.ByteString.Lazy (ByteString)

newtype UUID = UUID U.UUID
    deriving(Ord, Eq, Data, Typeable, Storable, NFData, Show, Read)

rndUUID :: IO UUID
rndUUID = nextRandom >>= return . UUID

unUUID :: UUID -> U.UUID
unUUID (UUID u) = u

nil :: UUID
nil = UUID U.nil

fromLazyASCIIBytes :: ByteString -> Maybe UUID
fromLazyASCIIBytes bs = UUID `fmap` U.fromLazyASCIIBytes bs

toLazyASCIIBytes :: UUID -> ByteString
toLazyASCIIBytes (UUID u) = U.toLazyASCIIBytes u

toText :: UUID -> Text
toText = txtUUID

fromText :: Text -> Maybe UUID
fromText = uuidTxt

fromWords :: Word32 -> Word32 -> Word32 -> Word32 -> UUID
fromWords w1 w2 w3 w4 = UUID (U.fromWords w1 w2 w3 w4)

toWords :: UUID -> (Word32, Word32, Word32, Word32)
toWords (UUID u) = U.toWords u

txtUUID :: UUID -> Text
txtUUID (UUID u) = uuidToText (U.toWords u) 
    where
        uuidToText :: (Word32, Word32, Word32, Word32) -> Text
        uuidToText (w0, w1, w2, w3) = hexw w0 $ hexw' w1 $ hexw' w2 $ hexw w3 T.empty
        hexw :: Word32 -> Text -> Text
        hexw  w s = hexn w 28 `T.cons` hexn w 24 `T.cons` hexn w 20 `T.cons` hexn w 16
                  `T.cons` hexn w 12 `T.cons` hexn w  8 `T.cons` hexn w  4 `T.cons` hexn w  0 `T.cons` s

        hexw' :: Word32 -> Text -> Text 
        hexw' w s = '-' `T.cons` hexn w 28 `T.cons` hexn w 24 `T.cons` hexn w 20 `T.cons` hexn w 16
                    `T.cons` '-' `T.cons` hexn w 12 `T.cons` hexn w  8 `T.cons` hexn w  4 `T.cons` hexn w  0 `T.cons` s

        hexn :: Word32 -> Int -> Char
        hexn w r = intToDigit $ fromIntegral ((w `shiftR` r) .&. 0xf)

uuidTxt :: Text -> Maybe UUID
uuidTxt t0 = do 
    (w0, t1) <- hexWord t0
    (w1, t2) <- hexWord t1
    (w2, t3) <- hexWord t2
    (w3, t4) <- hexWord t3
    if T.null t4 
        then Just $ UUID $ U.fromWords w0 w1 w2 w3
        else Nothing
    where hexWord :: Text -> Maybe (Word32, Text)
          hexWord s = Just (0, s) >>= hexByte >>= hexByte
                                  >>= hexByte >>= hexByte

          hexByte :: (Word32, Text) -> Maybe (Word32, Text)
          hexByte (w, txt) = if T.null txt then Nothing else 
            if T.head txt == '-'
                then hexByte (w, T.tail txt)
                else let hi = T.head txt
                         lo = T.head $ T.tail txt
                         ds = T.tail $ T.tail txt
                         bothHex = isHexDigit hi && isHexDigit lo
                         octet = fromIntegral (16 * digitToInt hi + digitToInt lo)
                     in if bothHex then Just ((w `shiftL` 8) .|. octet, ds)
                                   else Nothing

instance Hashable UUID where
    hash (UUID u) = hash u
    hashWithSalt s (UUID u) = hashWithSalt s u

instance SafeCopy UUID where
    putCopy (UUID u) = contain $ safePut $ U.toWords u
    getCopy = contain $ (UUID . (\(a,b,c,d) -> U.fromWords a b c d) <$> safeGet)

instance PathPiece UUID where
    fromPathPiece t = UUID <$> (fromString $ unpack t)
    toPathPiece = txtUUID

instance ToMarkup UUID where
    toMarkup u = toMarkup $ txtUUID u

instance ToJSON UUID where
    toJSON u = Aeson.String (txtUUID u)
instance FromJSON UUID where
    parseJSON (Aeson.String s) = case fromString (T.unpack s) of
                                    Just u -> return $ UUID u
                                    Nothing -> mzero
    parseJSON _ = mzero

instance Serialize UUID where
    put (UUID u) = put $ U.toWords u
    get = (UUID . (\(w1,w2,w3,w4) -> U.fromWords w1 w2 w3 w4)) `fmap` get