From f2d84f0a909a924fa7c624542cf539a2b5c005c3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 16 Jun 2013 16:49:27 -0700 Subject: [PATCH] Add Beast fork from JUCE commit 265fb0e8ebc26e1469d6edcc68d2ca9acefeb508 --- .gitignore | 24 +- Builds/VisualStudio2012/BeastConfig.h | 23 + Builds/VisualStudio2012/beast.vcxproj | 470 ++++ Builds/VisualStudio2012/beast.vcxproj.filters | 672 ++++- ReadMe.md | 6 + modules/beast_core/beast_core.cpp | 211 ++ modules/beast_core/beast_core.h | 436 ++- modules/beast_core/beast_core.mm | 23 + .../containers/beast_AbstractFifo.cpp | 229 ++ .../containers/beast_AbstractFifo.h | 217 ++ modules/beast_core/containers/beast_Array.h | 1047 +++++++ .../containers/beast_ArrayAllocationBase.h | 133 + .../containers/beast_DynamicObject.cpp | 74 + .../containers/beast_DynamicObject.h | 121 + .../containers/beast_ElementComparator.h | 274 ++ modules/beast_core/containers/beast_HashMap.h | 447 +++ .../containers/beast_LinkedListPointer.h | 366 +++ .../containers/beast_NamedValueSet.cpp | 304 +++ .../containers/beast_NamedValueSet.h | 164 ++ .../beast_core/containers/beast_OwnedArray.h | 865 ++++++ .../containers/beast_PropertySet.cpp | 218 ++ .../beast_core/containers/beast_PropertySet.h | 214 ++ .../containers/beast_ReferenceCountedArray.h | 855 ++++++ .../containers/beast_ScopedValueSetter.h | 95 + .../beast_core/containers/beast_SortedSet.h | 494 ++++ .../beast_core/containers/beast_SparseSet.h | 296 ++ .../beast_core/containers/beast_Variant.cpp | 704 +++++ modules/beast_core/containers/beast_Variant.h | 302 +++ .../files/beast_DirectoryIterator.cpp | 154 ++ .../files/beast_DirectoryIterator.h | 154 ++ modules/beast_core/files/beast_File.cpp | 1086 ++++++++ modules/beast_core/files/beast_File.h | 955 +++++++ .../files/beast_FileInputStream.cpp | 90 + .../beast_core/files/beast_FileInputStream.h | 94 + .../files/beast_FileOutputStream.cpp | 131 + .../beast_core/files/beast_FileOutputStream.h | 114 + .../beast_core/files/beast_FileSearchPath.cpp | 166 ++ .../beast_core/files/beast_FileSearchPath.h | 164 ++ .../beast_core/files/beast_MemoryMappedFile.h | 111 + .../beast_core/files/beast_TemporaryFile.cpp | 112 + .../beast_core/files/beast_TemporaryFile.h | 166 ++ modules/beast_core/json/beast_JSON.cpp | 645 +++++ modules/beast_core/json/beast_JSON.h | 113 + .../beast_core/logging/beast_FileLogger.cpp | 129 + modules/beast_core/logging/beast_FileLogger.h | 134 + modules/beast_core/logging/beast_Logger.cpp | 58 + modules/beast_core/logging/beast_Logger.h | 94 + modules/beast_core/maths/beast_BigInteger.cpp | 1016 +++++++ modules/beast_core/maths/beast_BigInteger.h | 329 +++ modules/beast_core/maths/beast_Expression.cpp | 1180 ++++++++ modules/beast_core/maths/beast_Expression.h | 269 ++ .../beast_core/maths/beast_MathsFunctions.h | 515 ++++ modules/beast_core/maths/beast_Random.cpp | 171 ++ modules/beast_core/maths/beast_Random.h | 135 + modules/beast_core/maths/beast_Range.h | 259 ++ modules/beast_core/memory/beast_Atomic.h | 388 +++ modules/beast_core/memory/beast_ByteOrder.h | 181 ++ modules/beast_core/memory/beast_HeapBlock.h | 303 +++ .../memory/beast_LeakedObjectDetector.h | 144 + modules/beast_core/memory/beast_Memory.h | 121 + .../beast_core/memory/beast_MemoryBlock.cpp | 416 +++ modules/beast_core/memory/beast_MemoryBlock.h | 255 ++ .../memory/beast_OptionalScopedPointer.h | 183 ++ .../memory/beast_ReferenceCountedObject.h | 395 +++ .../beast_core/memory/beast_ScopedPointer.h | 200 +- modules/beast_core/memory/beast_Singleton.h | 287 ++ .../beast_core/memory/beast_WeakReference.h | 209 ++ modules/beast_core/misc/beast_Result.cpp | 81 + modules/beast_core/misc/beast_Result.h | 119 + modules/beast_core/misc/beast_Uuid.cpp | 109 + modules/beast_core/misc/beast_Uuid.h | 109 + .../beast_core/misc/beast_WindowsRegistry.h | 119 + .../native/beast_BasicNativeHeaders.h | 220 ++ .../beast_core/native/beast_android_Files.cpp | 236 ++ .../native/beast_android_JNIHelpers.h | 402 +++ .../beast_core/native/beast_android_Misc.cpp | 27 + .../native/beast_android_Network.cpp | 171 ++ .../native/beast_android_SystemStats.cpp | 307 +++ .../native/beast_android_Threads.cpp | 77 + .../beast_core/native/beast_linux_Files.cpp | 373 +++ .../beast_core/native/beast_linux_Network.cpp | 457 ++++ .../native/beast_linux_SystemStats.cpp | 177 ++ .../beast_core/native/beast_linux_Threads.cpp | 85 + modules/beast_core/native/beast_mac_Files.mm | 480 ++++ .../beast_core/native/beast_mac_Network.mm | 427 +++ .../beast_core/native/beast_mac_Strings.mm | 91 + .../native/beast_mac_SystemStats.mm | 291 ++ .../beast_core/native/beast_mac_Threads.mm | 86 + .../beast_core/native/beast_osx_ObjCHelpers.h | 153 ++ .../native/beast_posix_NamedPipe.cpp | 214 ++ .../native/beast_posix_SharedCode.h | 1251 +++++++++ .../native/beast_win32_ComSmartPtr.h | 165 ++ .../beast_core/native/beast_win32_Files.cpp | 956 +++++++ .../beast_core/native/beast_win32_Network.cpp | 464 ++++ .../native/beast_win32_Registry.cpp | 216 ++ .../native/beast_win32_SystemStats.cpp | 413 +++ .../beast_core/native/beast_win32_Threads.cpp | 634 +++++ .../native/java/BeastAppActivity.java | 697 +++++ .../beast_core/network/beast_IPAddress.cpp | 144 + modules/beast_core/network/beast_IPAddress.h | 77 + .../beast_core/network/beast_MACAddress.cpp | 73 + modules/beast_core/network/beast_MACAddress.h | 84 + .../beast_core/network/beast_NamedPipe.cpp | 61 + modules/beast_core/network/beast_NamedPipe.h | 100 + modules/beast_core/network/beast_Socket.cpp | 585 ++++ modules/beast_core/network/beast_Socket.h | 302 +++ modules/beast_core/network/beast_URL.cpp | 468 ++++ modules/beast_core/network/beast_URL.h | 345 +++ .../streams/beast_BufferedInputStream.cpp | 193 ++ .../streams/beast_BufferedInputStream.h | 91 + .../streams/beast_FileInputSource.cpp | 51 + .../streams/beast_FileInputSource.h | 64 + .../beast_core/streams/beast_InputSource.h | 74 + .../beast_core/streams/beast_InputStream.cpp | 231 ++ .../beast_core/streams/beast_InputStream.h | 293 ++ .../streams/beast_MemoryInputStream.cpp | 155 ++ .../streams/beast_MemoryInputStream.h | 95 + .../streams/beast_MemoryOutputStream.cpp | 165 ++ .../streams/beast_MemoryOutputStream.h | 131 + .../beast_core/streams/beast_OutputStream.cpp | 320 +++ .../beast_core/streams/beast_OutputStream.h | 262 ++ .../streams/beast_SubregionStream.cpp | 77 + .../streams/beast_SubregionStream.h | 87 + .../beast_core/system/beast_PlatformDefs.h | 352 +++ .../beast_core/system/beast_StandardHeader.h | 166 ++ .../beast_core/system/beast_SystemStats.cpp | 152 ++ modules/beast_core/system/beast_SystemStats.h | 201 ++ .../beast_core/system/beast_TargetPlatform.h | 194 ++ .../beast_core/text/beast_CharPointer_ASCII.h | 382 +++ .../beast_core/text/beast_CharPointer_UTF16.h | 496 ++++ .../beast_core/text/beast_CharPointer_UTF32.h | 373 +++ .../beast_core/text/beast_CharPointer_UTF8.h | 560 ++++ .../text/beast_CharacterFunctions.cpp | 149 + .../text/beast_CharacterFunctions.h | 585 ++++ modules/beast_core/text/beast_Identifier.cpp | 69 + modules/beast_core/text/beast_Identifier.h | 106 + .../text/beast_LocalisedStrings.cpp | 182 ++ .../beast_core/text/beast_LocalisedStrings.h | 219 ++ modules/beast_core/text/beast_NewLine.h | 73 + modules/beast_core/text/beast_String.cpp | 2406 +++++++++++++++++ modules/beast_core/text/beast_String.h | 1346 +++++++++ modules/beast_core/text/beast_StringArray.cpp | 505 ++++ modules/beast_core/text/beast_StringArray.h | 406 +++ .../beast_core/text/beast_StringPairArray.cpp | 137 + .../beast_core/text/beast_StringPairArray.h | 158 ++ modules/beast_core/text/beast_StringPool.cpp | 109 + modules/beast_core/text/beast_StringPool.h | 90 + modules/beast_core/text/beast_TextDiff.cpp | 236 ++ modules/beast_core/text/beast_TextDiff.h | 75 + .../beast_core/threads/beast_ChildProcess.cpp | 88 + .../beast_core/threads/beast_ChildProcess.h | 100 + .../threads/beast_CriticalSection.h | 252 ++ .../beast_core/threads/beast_DynamicLibrary.h | 80 + .../threads/beast_HighResolutionTimer.cpp | 31 + .../threads/beast_HighResolutionTimer.h | 104 + .../threads/beast_InterProcessLock.h | 126 + modules/beast_core/threads/beast_Process.h | 147 + .../threads/beast_ReadWriteLock.cpp | 153 ++ .../beast_core/threads/beast_ReadWriteLock.h | 154 ++ modules/beast_core/threads/beast_ScopedLock.h | 232 ++ .../beast_core/threads/beast_ScopedReadLock.h | 87 + .../threads/beast_ScopedWriteLock.h | 87 + modules/beast_core/threads/beast_SpinLock.h | 88 + modules/beast_core/threads/beast_Thread.cpp | 358 +++ modules/beast_core/threads/beast_Thread.h | 287 ++ .../threads/beast_ThreadLocalValue.h | 194 ++ .../beast_core/threads/beast_ThreadPool.cpp | 375 +++ modules/beast_core/threads/beast_ThreadPool.h | 313 +++ .../threads/beast_TimeSliceThread.cpp | 166 ++ .../threads/beast_TimeSliceThread.h | 147 + .../beast_core/threads/beast_WaitableEvent.h | 114 + .../time/beast_PerformanceCounter.cpp | 90 + .../time/beast_PerformanceCounter.h | 103 + .../beast_core/time/beast_RelativeTime.cpp | 133 + modules/beast_core/time/beast_RelativeTime.h | 176 ++ modules/beast_core/time/beast_Time.cpp | 441 +++ modules/beast_core/time/beast_Time.h | 401 +++ .../beast_core/unit_tests/beast_UnitTest.cpp | 232 ++ .../beast_core/unit_tests/beast_UnitTest.h | 285 ++ modules/beast_core/xml/beast_XmlDocument.cpp | 848 ++++++ modules/beast_core/xml/beast_XmlDocument.h | 181 ++ modules/beast_core/xml/beast_XmlElement.cpp | 824 ++++++ modules/beast_core/xml/beast_XmlElement.h | 734 +++++ .../zip/beast_GZIPCompressorOutputStream.cpp | 213 ++ .../zip/beast_GZIPCompressorOutputStream.h | 105 + .../zip/beast_GZIPDecompressorInputStream.cpp | 290 ++ .../zip/beast_GZIPDecompressorInputStream.h | 102 + modules/beast_core/zip/beast_ZipFile.cpp | 597 ++++ modules/beast_core/zip/beast_ZipFile.h | 249 ++ modules/beast_core/zip/zlib/README | 125 + modules/beast_core/zip/zlib/adler32.c | 143 + modules/beast_core/zip/zlib/compress.c | 70 + modules/beast_core/zip/zlib/crc32.c | 407 +++ modules/beast_core/zip/zlib/crc32.h | 441 +++ modules/beast_core/zip/zlib/deflate.c | 1679 ++++++++++++ modules/beast_core/zip/zlib/deflate.h | 333 +++ modules/beast_core/zip/zlib/infback.c | 611 +++++ modules/beast_core/zip/zlib/inffast.c | 316 +++ modules/beast_core/zip/zlib/inffast.h | 11 + modules/beast_core/zip/zlib/inffixed.h | 94 + modules/beast_core/zip/zlib/inflate.c | 1339 +++++++++ modules/beast_core/zip/zlib/inflate.h | 121 + modules/beast_core/zip/zlib/inftrees.c | 328 +++ modules/beast_core/zip/zlib/inftrees.h | 61 + modules/beast_core/zip/zlib/trees.c | 1191 ++++++++ modules/beast_core/zip/zlib/trees.h | 127 + modules/beast_core/zip/zlib/uncompr.c | 60 + modules/beast_core/zip/zlib/zconf.h | 345 +++ modules/beast_core/zip/zlib/zconf.in.h | 332 +++ modules/beast_core/zip/zlib/zlib.h | 1358 ++++++++++ modules/beast_core/zip/zlib/zutil.c | 311 +++ modules/beast_core/zip/zlib/zutil.h | 271 ++ 212 files changed, 64506 insertions(+), 27 deletions(-) create mode 100644 Builds/VisualStudio2012/BeastConfig.h create mode 100644 modules/beast_core/containers/beast_AbstractFifo.cpp create mode 100644 modules/beast_core/containers/beast_AbstractFifo.h create mode 100644 modules/beast_core/containers/beast_Array.h create mode 100644 modules/beast_core/containers/beast_ArrayAllocationBase.h create mode 100644 modules/beast_core/containers/beast_DynamicObject.cpp create mode 100644 modules/beast_core/containers/beast_DynamicObject.h create mode 100644 modules/beast_core/containers/beast_ElementComparator.h create mode 100644 modules/beast_core/containers/beast_HashMap.h create mode 100644 modules/beast_core/containers/beast_LinkedListPointer.h create mode 100644 modules/beast_core/containers/beast_NamedValueSet.cpp create mode 100644 modules/beast_core/containers/beast_NamedValueSet.h create mode 100644 modules/beast_core/containers/beast_OwnedArray.h create mode 100644 modules/beast_core/containers/beast_PropertySet.cpp create mode 100644 modules/beast_core/containers/beast_PropertySet.h create mode 100644 modules/beast_core/containers/beast_ReferenceCountedArray.h create mode 100644 modules/beast_core/containers/beast_ScopedValueSetter.h create mode 100644 modules/beast_core/containers/beast_SortedSet.h create mode 100644 modules/beast_core/containers/beast_SparseSet.h create mode 100644 modules/beast_core/containers/beast_Variant.cpp create mode 100644 modules/beast_core/containers/beast_Variant.h create mode 100644 modules/beast_core/files/beast_DirectoryIterator.cpp create mode 100644 modules/beast_core/files/beast_DirectoryIterator.h create mode 100644 modules/beast_core/files/beast_File.cpp create mode 100644 modules/beast_core/files/beast_File.h create mode 100644 modules/beast_core/files/beast_FileInputStream.cpp create mode 100644 modules/beast_core/files/beast_FileInputStream.h create mode 100644 modules/beast_core/files/beast_FileOutputStream.cpp create mode 100644 modules/beast_core/files/beast_FileOutputStream.h create mode 100644 modules/beast_core/files/beast_FileSearchPath.cpp create mode 100644 modules/beast_core/files/beast_FileSearchPath.h create mode 100644 modules/beast_core/files/beast_MemoryMappedFile.h create mode 100644 modules/beast_core/files/beast_TemporaryFile.cpp create mode 100644 modules/beast_core/files/beast_TemporaryFile.h create mode 100644 modules/beast_core/json/beast_JSON.cpp create mode 100644 modules/beast_core/json/beast_JSON.h create mode 100644 modules/beast_core/logging/beast_FileLogger.cpp create mode 100644 modules/beast_core/logging/beast_FileLogger.h create mode 100644 modules/beast_core/logging/beast_Logger.cpp create mode 100644 modules/beast_core/logging/beast_Logger.h create mode 100644 modules/beast_core/maths/beast_BigInteger.cpp create mode 100644 modules/beast_core/maths/beast_BigInteger.h create mode 100644 modules/beast_core/maths/beast_Expression.cpp create mode 100644 modules/beast_core/maths/beast_Expression.h create mode 100644 modules/beast_core/maths/beast_MathsFunctions.h create mode 100644 modules/beast_core/maths/beast_Random.cpp create mode 100644 modules/beast_core/maths/beast_Random.h create mode 100644 modules/beast_core/maths/beast_Range.h create mode 100644 modules/beast_core/memory/beast_Atomic.h create mode 100644 modules/beast_core/memory/beast_ByteOrder.h create mode 100644 modules/beast_core/memory/beast_HeapBlock.h create mode 100644 modules/beast_core/memory/beast_LeakedObjectDetector.h create mode 100644 modules/beast_core/memory/beast_Memory.h create mode 100644 modules/beast_core/memory/beast_MemoryBlock.cpp create mode 100644 modules/beast_core/memory/beast_MemoryBlock.h create mode 100644 modules/beast_core/memory/beast_OptionalScopedPointer.h create mode 100644 modules/beast_core/memory/beast_ReferenceCountedObject.h create mode 100644 modules/beast_core/memory/beast_Singleton.h create mode 100644 modules/beast_core/memory/beast_WeakReference.h create mode 100644 modules/beast_core/misc/beast_Result.cpp create mode 100644 modules/beast_core/misc/beast_Result.h create mode 100644 modules/beast_core/misc/beast_Uuid.cpp create mode 100644 modules/beast_core/misc/beast_Uuid.h create mode 100644 modules/beast_core/misc/beast_WindowsRegistry.h create mode 100644 modules/beast_core/native/beast_BasicNativeHeaders.h create mode 100644 modules/beast_core/native/beast_android_Files.cpp create mode 100644 modules/beast_core/native/beast_android_JNIHelpers.h create mode 100644 modules/beast_core/native/beast_android_Misc.cpp create mode 100644 modules/beast_core/native/beast_android_Network.cpp create mode 100644 modules/beast_core/native/beast_android_SystemStats.cpp create mode 100644 modules/beast_core/native/beast_android_Threads.cpp create mode 100644 modules/beast_core/native/beast_linux_Files.cpp create mode 100644 modules/beast_core/native/beast_linux_Network.cpp create mode 100644 modules/beast_core/native/beast_linux_SystemStats.cpp create mode 100644 modules/beast_core/native/beast_linux_Threads.cpp create mode 100644 modules/beast_core/native/beast_mac_Files.mm create mode 100644 modules/beast_core/native/beast_mac_Network.mm create mode 100644 modules/beast_core/native/beast_mac_Strings.mm create mode 100644 modules/beast_core/native/beast_mac_SystemStats.mm create mode 100644 modules/beast_core/native/beast_mac_Threads.mm create mode 100644 modules/beast_core/native/beast_osx_ObjCHelpers.h create mode 100644 modules/beast_core/native/beast_posix_NamedPipe.cpp create mode 100644 modules/beast_core/native/beast_posix_SharedCode.h create mode 100644 modules/beast_core/native/beast_win32_ComSmartPtr.h create mode 100644 modules/beast_core/native/beast_win32_Files.cpp create mode 100644 modules/beast_core/native/beast_win32_Network.cpp create mode 100644 modules/beast_core/native/beast_win32_Registry.cpp create mode 100644 modules/beast_core/native/beast_win32_SystemStats.cpp create mode 100644 modules/beast_core/native/beast_win32_Threads.cpp create mode 100644 modules/beast_core/native/java/BeastAppActivity.java create mode 100644 modules/beast_core/network/beast_IPAddress.cpp create mode 100644 modules/beast_core/network/beast_IPAddress.h create mode 100644 modules/beast_core/network/beast_MACAddress.cpp create mode 100644 modules/beast_core/network/beast_MACAddress.h create mode 100644 modules/beast_core/network/beast_NamedPipe.cpp create mode 100644 modules/beast_core/network/beast_NamedPipe.h create mode 100644 modules/beast_core/network/beast_Socket.cpp create mode 100644 modules/beast_core/network/beast_Socket.h create mode 100644 modules/beast_core/network/beast_URL.cpp create mode 100644 modules/beast_core/network/beast_URL.h create mode 100644 modules/beast_core/streams/beast_BufferedInputStream.cpp create mode 100644 modules/beast_core/streams/beast_BufferedInputStream.h create mode 100644 modules/beast_core/streams/beast_FileInputSource.cpp create mode 100644 modules/beast_core/streams/beast_FileInputSource.h create mode 100644 modules/beast_core/streams/beast_InputSource.h create mode 100644 modules/beast_core/streams/beast_InputStream.cpp create mode 100644 modules/beast_core/streams/beast_InputStream.h create mode 100644 modules/beast_core/streams/beast_MemoryInputStream.cpp create mode 100644 modules/beast_core/streams/beast_MemoryInputStream.h create mode 100644 modules/beast_core/streams/beast_MemoryOutputStream.cpp create mode 100644 modules/beast_core/streams/beast_MemoryOutputStream.h create mode 100644 modules/beast_core/streams/beast_OutputStream.cpp create mode 100644 modules/beast_core/streams/beast_OutputStream.h create mode 100644 modules/beast_core/streams/beast_SubregionStream.cpp create mode 100644 modules/beast_core/streams/beast_SubregionStream.h create mode 100644 modules/beast_core/system/beast_PlatformDefs.h create mode 100644 modules/beast_core/system/beast_StandardHeader.h create mode 100644 modules/beast_core/system/beast_SystemStats.cpp create mode 100644 modules/beast_core/system/beast_SystemStats.h create mode 100644 modules/beast_core/system/beast_TargetPlatform.h create mode 100644 modules/beast_core/text/beast_CharPointer_ASCII.h create mode 100644 modules/beast_core/text/beast_CharPointer_UTF16.h create mode 100644 modules/beast_core/text/beast_CharPointer_UTF32.h create mode 100644 modules/beast_core/text/beast_CharPointer_UTF8.h create mode 100644 modules/beast_core/text/beast_CharacterFunctions.cpp create mode 100644 modules/beast_core/text/beast_CharacterFunctions.h create mode 100644 modules/beast_core/text/beast_Identifier.cpp create mode 100644 modules/beast_core/text/beast_Identifier.h create mode 100644 modules/beast_core/text/beast_LocalisedStrings.cpp create mode 100644 modules/beast_core/text/beast_LocalisedStrings.h create mode 100644 modules/beast_core/text/beast_NewLine.h create mode 100644 modules/beast_core/text/beast_String.cpp create mode 100644 modules/beast_core/text/beast_String.h create mode 100644 modules/beast_core/text/beast_StringArray.cpp create mode 100644 modules/beast_core/text/beast_StringArray.h create mode 100644 modules/beast_core/text/beast_StringPairArray.cpp create mode 100644 modules/beast_core/text/beast_StringPairArray.h create mode 100644 modules/beast_core/text/beast_StringPool.cpp create mode 100644 modules/beast_core/text/beast_StringPool.h create mode 100644 modules/beast_core/text/beast_TextDiff.cpp create mode 100644 modules/beast_core/text/beast_TextDiff.h create mode 100644 modules/beast_core/threads/beast_ChildProcess.cpp create mode 100644 modules/beast_core/threads/beast_ChildProcess.h create mode 100644 modules/beast_core/threads/beast_CriticalSection.h create mode 100644 modules/beast_core/threads/beast_DynamicLibrary.h create mode 100644 modules/beast_core/threads/beast_HighResolutionTimer.cpp create mode 100644 modules/beast_core/threads/beast_HighResolutionTimer.h create mode 100644 modules/beast_core/threads/beast_InterProcessLock.h create mode 100644 modules/beast_core/threads/beast_Process.h create mode 100644 modules/beast_core/threads/beast_ReadWriteLock.cpp create mode 100644 modules/beast_core/threads/beast_ReadWriteLock.h create mode 100644 modules/beast_core/threads/beast_ScopedLock.h create mode 100644 modules/beast_core/threads/beast_ScopedReadLock.h create mode 100644 modules/beast_core/threads/beast_ScopedWriteLock.h create mode 100644 modules/beast_core/threads/beast_SpinLock.h create mode 100644 modules/beast_core/threads/beast_Thread.cpp create mode 100644 modules/beast_core/threads/beast_Thread.h create mode 100644 modules/beast_core/threads/beast_ThreadLocalValue.h create mode 100644 modules/beast_core/threads/beast_ThreadPool.cpp create mode 100644 modules/beast_core/threads/beast_ThreadPool.h create mode 100644 modules/beast_core/threads/beast_TimeSliceThread.cpp create mode 100644 modules/beast_core/threads/beast_TimeSliceThread.h create mode 100644 modules/beast_core/threads/beast_WaitableEvent.h create mode 100644 modules/beast_core/time/beast_PerformanceCounter.cpp create mode 100644 modules/beast_core/time/beast_PerformanceCounter.h create mode 100644 modules/beast_core/time/beast_RelativeTime.cpp create mode 100644 modules/beast_core/time/beast_RelativeTime.h create mode 100644 modules/beast_core/time/beast_Time.cpp create mode 100644 modules/beast_core/time/beast_Time.h create mode 100644 modules/beast_core/unit_tests/beast_UnitTest.cpp create mode 100644 modules/beast_core/unit_tests/beast_UnitTest.h create mode 100644 modules/beast_core/xml/beast_XmlDocument.cpp create mode 100644 modules/beast_core/xml/beast_XmlDocument.h create mode 100644 modules/beast_core/xml/beast_XmlElement.cpp create mode 100644 modules/beast_core/xml/beast_XmlElement.h create mode 100644 modules/beast_core/zip/beast_GZIPCompressorOutputStream.cpp create mode 100644 modules/beast_core/zip/beast_GZIPCompressorOutputStream.h create mode 100644 modules/beast_core/zip/beast_GZIPDecompressorInputStream.cpp create mode 100644 modules/beast_core/zip/beast_GZIPDecompressorInputStream.h create mode 100644 modules/beast_core/zip/beast_ZipFile.cpp create mode 100644 modules/beast_core/zip/beast_ZipFile.h create mode 100644 modules/beast_core/zip/zlib/README create mode 100644 modules/beast_core/zip/zlib/adler32.c create mode 100644 modules/beast_core/zip/zlib/compress.c create mode 100644 modules/beast_core/zip/zlib/crc32.c create mode 100644 modules/beast_core/zip/zlib/crc32.h create mode 100644 modules/beast_core/zip/zlib/deflate.c create mode 100644 modules/beast_core/zip/zlib/deflate.h create mode 100644 modules/beast_core/zip/zlib/infback.c create mode 100644 modules/beast_core/zip/zlib/inffast.c create mode 100644 modules/beast_core/zip/zlib/inffast.h create mode 100644 modules/beast_core/zip/zlib/inffixed.h create mode 100644 modules/beast_core/zip/zlib/inflate.c create mode 100644 modules/beast_core/zip/zlib/inflate.h create mode 100644 modules/beast_core/zip/zlib/inftrees.c create mode 100644 modules/beast_core/zip/zlib/inftrees.h create mode 100644 modules/beast_core/zip/zlib/trees.c create mode 100644 modules/beast_core/zip/zlib/trees.h create mode 100644 modules/beast_core/zip/zlib/uncompr.c create mode 100644 modules/beast_core/zip/zlib/zconf.h create mode 100644 modules/beast_core/zip/zlib/zconf.in.h create mode 100644 modules/beast_core/zip/zlib/zlib.h create mode 100644 modules/beast_core/zip/zlib/zutil.c create mode 100644 modules/beast_core/zip/zlib/zutil.h diff --git a/.gitignore b/.gitignore index 86b5dea12c..b7d07bdfc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,25 @@ +._* +*.mode1v3 +*.pbxuser +*.perspectivev3 +*.user +*.ncb +*.suo +*.obj +*.ilk +*.pch +*.pdb +*.dep +*.idb +*.manifest +*.manifest.res +*.o +*.d +*.sdf +xcuserdata +contents.xcworkspacedata +.DS_Store +.svn +profile Builds/VisualStudio2012/Debug Builds/VisualStudio2012/Release -Builds/VisualStudio2012/*.user diff --git a/Builds/VisualStudio2012/BeastConfig.h b/Builds/VisualStudio2012/BeastConfig.h new file mode 100644 index 0000000000..29fd836456 --- /dev/null +++ b/Builds/VisualStudio2012/BeastConfig.h @@ -0,0 +1,23 @@ + +#ifndef BEAST_BEASTCONFIG_HEADER +#define BEAST_BEASTCONFIG_HEADER + +// beast_core flags: + +#ifndef BEAST_FORCE_DEBUG + //#define BEAST_FORCE_DEBUG +#endif + +#ifndef BEAST_LOG_ASSERTIONS + //#define BEAST_LOG_ASSERTIONS 1 +#endif + +#ifndef BEAST_CHECK_MEMORY_LEAKS + //#define BEAST_CHECK_MEMORY_LEAKS +#endif + +#ifndef BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES + //#define BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES +#endif + +#endif diff --git a/Builds/VisualStudio2012/beast.vcxproj b/Builds/VisualStudio2012/beast.vcxproj index 9fe1e1d3bf..5ecfa00779 100644 --- a/Builds/VisualStudio2012/beast.vcxproj +++ b/Builds/VisualStudio2012/beast.vcxproj @@ -13,14 +13,482 @@ + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + {73C5A0F0-7629-4DE7-9194-BE7AC6C19535} @@ -59,6 +527,7 @@ Level3 Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(ProjectDir) Windows @@ -74,6 +543,7 @@ true true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(ProjectDir) Windows diff --git a/Builds/VisualStudio2012/beast.vcxproj.filters b/Builds/VisualStudio2012/beast.vcxproj.filters index d690caaf4b..1867cf7991 100644 --- a/Builds/VisualStudio2012/beast.vcxproj.filters +++ b/Builds/VisualStudio2012/beast.vcxproj.filters @@ -6,26 +6,690 @@ beast_core + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\zip\zlib + + + beast_core\native\java + {6dafd8d5-2901-4b41-85b7-52f6e86baacc} + + {7cc041c8-fdf4-4e98-a56a-df516c2a9aa2} + + + {3b7d8d7e-eabc-423c-a631-2ff37bf9b9eb} + + + {abe24d69-c1d1-49e2-98a1-1e7bf760b97a} + + + {0fa6b76b-305f-473c-9b94-c1028a3af3fc} + + + {e2339099-bb8e-4437-ae8b-d4f64ef1e3f3} + - {883f776d-bec2-4e7a-b4e8-726ed3039071} + {a89aa17e-4e01-4c2d-ba7e-196e9e0b67bb} + + + {64a314e1-0361-428a-a294-0615d8140f58} + + + {1d018a70-71b3-4d70-9280-02efd4f348de} + + + {ad675f57-9303-4712-9ff2-c7f59f959e7b} + + + {016d839c-4860-4bc3-8f6c-e965f50c2bfa} + + + {7fb77af6-ce16-4f53-823e-906963b42985} + + + {4603580c-5668-4e70-bed8-77f00f03f0ff} + + + {3409dc7a-92b5-43aa-b678-fde9f82ced55} + + + {d3ea4af5-ae48-4144-a2ef-a212342d72ee} + + + {0a8a6870-87a4-4f19-bbd3-b277f94bb83e} + + + {a1a38659-7779-41d6-8a3c-068433c4deaf} + + + {df5f5a69-5919-4a24-bbce-b3f87e4903cf} + + + {b0d206d9-c002-4be1-b503-4ad16aca838a} + + + {31038502-9139-4c19-bd67-8f90f08a70ca} - - beast_core\memory - beast_core + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\json + + + beast_core\logging + + + beast_core\logging + + + beast_core\maths + + + beast_core\maths + + + beast_core\maths + + + beast_core\maths + + + beast_core\maths + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\memory + + + beast_core\misc + + + beast_core\misc + + + beast_core\misc + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\network + + + beast_core\network + + + beast_core\network + + + beast_core\network + + + beast_core\network + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\system + + + beast_core\system + + + beast_core\system + + + beast_core\system + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\time + + + beast_core\time + + + beast_core\time + + + beast_core\unit_tests + + + beast_core\xml + + + beast_core\xml + + + beast_core\zip + + + beast_core\zip + + + beast_core\zip + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + beast_core + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\containers + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\files + + + beast_core\json + + + beast_core\logging + + + beast_core\logging + + + beast_core\maths + + + beast_core\maths + + + beast_core\maths + + + beast_core\memory + + + beast_core\misc + + + beast_core\misc + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\native + + + beast_core\network + + + beast_core\network + + + beast_core\network + + + beast_core\network + + + beast_core\network + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\streams + + + beast_core\system + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\text + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\threads + + + beast_core\time + + + beast_core\time + + + beast_core\time + + + beast_core\unit_tests + + + beast_core\xml + + + beast_core\xml + + + beast_core\zip + + + beast_core\zip + + + beast_core\zip + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + + + beast_core\zip\zlib + \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md index 9c8689bda5..5acd2f38c2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -6,3 +6,9 @@ No external dependencies, no complicated build steps. Things people need for building peer to peer, concurrent, cryptographic systems. The hope is that this will replace the use of boost and other cumbersome jalopies. + +## JUCE + +Beast is based on the juce_core module which is provided under the ISC +license. More information about JUCE is available at +http://www.juce.com diff --git a/modules/beast_core/beast_core.cpp b/modules/beast_core/beast_core.cpp index 939a512ed3..d6eca85369 100644 --- a/modules/beast_core/beast_core.cpp +++ b/modules/beast_core/beast_core.cpp @@ -1,2 +1,213 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#if defined (BEAST_CORE_BEASTHEADER) && ! BEAST_AMALGAMATED_INCLUDE + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of BEAST cpp file" +#endif + +// Your project must contain a BeastConfig.h file with your project-specific settings in it, +// and your header search path must make it accessible to the module's files. +#include "BeastConfig.h" + +//============================================================================== +#include "native/beast_BasicNativeHeaders.h" #include "beast_core.h" + +#include +#include +#include + +#if ! BEAST_ANDROID + #include +#endif + +#if BEAST_WINDOWS + #include + #include + #include + + #if ! BEAST_MINGW + #include + + #if ! BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #pragma comment (lib, "DbgHelp.lib") + #endif + #endif + + #if BEAST_MINGW + #include + #endif + +#else + #if BEAST_LINUX || BEAST_ANDROID + #include + #include + #include + #include + #include + #endif + + #if BEAST_LINUX + #include + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + + #if ! BEAST_ANDROID + #include + #endif +#endif + +#if BEAST_MAC || BEAST_IOS + #include + #include +#endif + +#if BEAST_ANDROID + #include +#endif + + +//============================================================================== +namespace beast +{ + +#include "containers/beast_AbstractFifo.cpp" +#include "containers/beast_DynamicObject.cpp" +#include "containers/beast_NamedValueSet.cpp" +#include "containers/beast_PropertySet.cpp" +#include "containers/beast_Variant.cpp" +#include "files/beast_DirectoryIterator.cpp" +#include "files/beast_File.cpp" +#include "files/beast_FileInputStream.cpp" +#include "files/beast_FileOutputStream.cpp" +#include "files/beast_FileSearchPath.cpp" +#include "files/beast_TemporaryFile.cpp" +#include "json/beast_JSON.cpp" +#include "logging/beast_FileLogger.cpp" +#include "logging/beast_Logger.cpp" +#include "maths/beast_BigInteger.cpp" +#include "maths/beast_Expression.cpp" +#include "maths/beast_Random.cpp" +#include "memory/beast_MemoryBlock.cpp" +#include "misc/beast_Result.cpp" +#include "misc/beast_Uuid.cpp" +#include "network/beast_MACAddress.cpp" +#include "network/beast_NamedPipe.cpp" +#include "network/beast_Socket.cpp" +#include "network/beast_URL.cpp" +#include "network/beast_IPAddress.cpp" +#include "streams/beast_BufferedInputStream.cpp" +#include "streams/beast_FileInputSource.cpp" +#include "streams/beast_InputStream.cpp" +#include "streams/beast_MemoryInputStream.cpp" +#include "streams/beast_MemoryOutputStream.cpp" +#include "streams/beast_OutputStream.cpp" +#include "streams/beast_SubregionStream.cpp" +#include "system/beast_SystemStats.cpp" +#include "text/beast_CharacterFunctions.cpp" +#include "text/beast_Identifier.cpp" +#include "text/beast_LocalisedStrings.cpp" +#include "text/beast_String.cpp" +#include "text/beast_StringArray.cpp" +#include "text/beast_StringPairArray.cpp" +#include "text/beast_StringPool.cpp" +#include "text/beast_TextDiff.cpp" +#include "threads/beast_ChildProcess.cpp" +#include "threads/beast_ReadWriteLock.cpp" +#include "threads/beast_Thread.cpp" +#include "threads/beast_ThreadPool.cpp" +#include "threads/beast_TimeSliceThread.cpp" +#include "time/beast_PerformanceCounter.cpp" +#include "time/beast_RelativeTime.cpp" +#include "time/beast_Time.cpp" +#include "unit_tests/beast_UnitTest.cpp" +#include "xml/beast_XmlDocument.cpp" +#include "xml/beast_XmlElement.cpp" +#include "zip/beast_GZIPDecompressorInputStream.cpp" +#include "zip/beast_GZIPCompressorOutputStream.cpp" +#include "zip/beast_ZipFile.cpp" + +//============================================================================== +#if BEAST_MAC || BEAST_IOS +#include "native/beast_osx_ObjCHelpers.h" +#endif + +#if BEAST_ANDROID +#include "native/beast_android_JNIHelpers.h" +#endif + +#if ! BEAST_WINDOWS +#include "native/beast_posix_SharedCode.h" +#include "native/beast_posix_NamedPipe.cpp" +#endif + +//============================================================================== +#if BEAST_MAC || BEAST_IOS +#include "native/beast_mac_Files.mm" +#include "native/beast_mac_Network.mm" +#include "native/beast_mac_Strings.mm" +#include "native/beast_mac_SystemStats.mm" +#include "native/beast_mac_Threads.mm" + +//============================================================================== +#elif BEAST_WINDOWS +#include "native/beast_win32_ComSmartPtr.h" +#include "native/beast_win32_Files.cpp" +#include "native/beast_win32_Network.cpp" +#include "native/beast_win32_Registry.cpp" +#include "native/beast_win32_SystemStats.cpp" +#include "native/beast_win32_Threads.cpp" + +//============================================================================== +#elif BEAST_LINUX +#include "native/beast_linux_Files.cpp" +#include "native/beast_linux_Network.cpp" +#include "native/beast_linux_SystemStats.cpp" +#include "native/beast_linux_Threads.cpp" + +//============================================================================== +#elif BEAST_ANDROID +#include "native/beast_android_Files.cpp" +#include "native/beast_android_Misc.cpp" +#include "native/beast_android_Network.cpp" +#include "native/beast_android_SystemStats.cpp" +#include "native/beast_android_Threads.cpp" + +#endif + +#include "threads/beast_HighResolutionTimer.cpp" + +} diff --git a/modules/beast_core/beast_core.h b/modules/beast_core/beast_core.h index 65284cd1e8..8c829e9548 100644 --- a/modules/beast_core/beast_core.h +++ b/modules/beast_core/beast_core.h @@ -1,10 +1,440 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco -#include -#include + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CORE_BEASTHEADER +#define BEAST_CORE_BEASTHEADER + +#ifndef BEAST_BEASTCONFIG_HEADER + /* If you fail to make sure that all your compile units are building Beast with the same set of + option flags, then there's a risk that different compile units will treat the classes as having + different memory layouts, leading to very nasty memory corruption errors when they all get + linked together. That's why it's best to always include the BeastConfig.h file before any beast headers. + */ + #ifdef _MSC_VER +#pragma message ("Have you included your BeastConfig.h file before including the Beast headers?") + #else + #warning "Have you included your BeastConfig.h file before including the Beast headers?" + #endif +#endif + +//============================================================================== +#include "system/beast_TargetPlatform.h" + +//============================================================================= +/** Config: BEAST_FORCE_DEBUG + + Normally, BEAST_DEBUG is set to 1 or 0 based on compiler and project settings, + but if you define this value, you can override this to force it to be true or false. +*/ +#ifndef BEAST_FORCE_DEBUG + //#define BEAST_FORCE_DEBUG 0 +#endif + +//============================================================================= +/** Config: BEAST_LOG_ASSERTIONS + + If this flag is enabled, the the bassert and jassertfalse macros will always use Logger::writeToLog() + to write a message when an assertion happens. + + Enabling it will also leave this turned on in release builds. When it's disabled, + however, the bassert and jassertfalse macros will not be compiled in a + release build. + + @see bassert, jassertfalse, Logger +*/ +#ifndef BEAST_LOG_ASSERTIONS + #if BEAST_ANDROID + #define BEAST_LOG_ASSERTIONS 1 + #else + #define BEAST_LOG_ASSERTIONS 0 + #endif +#endif + +//============================================================================= +/** Config: BEAST_CHECK_MEMORY_LEAKS + + Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector + class and the BEAST_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes. +*/ +#if BEAST_DEBUG && ! defined (BEAST_CHECK_MEMORY_LEAKS) + #define BEAST_CHECK_MEMORY_LEAKS 1 +#endif + +//============================================================================= +/** Config: BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES + + In a Visual C++ build, this can be used to stop the required system libs being + automatically added to the link stage. +*/ +#ifndef BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #define BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 +#endif + +/* Config: BEAST_INCLUDE_ZLIB_CODE + This can be used to disable Beast's embedded 3rd-party zlib code. + You might need to tweak this if you're linking to an external zlib library in your app, + but for normal apps, this option should be left alone. + + If you disable this, you might also want to set a value for BEAST_ZLIB_INCLUDE_PATH, to + specify the path where your zlib headers live. +*/ +#ifndef BEAST_INCLUDE_ZLIB_CODE + #define BEAST_INCLUDE_ZLIB_CODE 1 +#endif + +#ifndef BEAST_ZLIB_INCLUDE_PATH + #define BEAST_ZLIB_INCLUDE_PATH +#endif + +/* Config: BEAST_CATCH_UNHANDLED_EXCEPTIONS + If enabled, this will add some exception-catching code to forward unhandled exceptions + to your BEASTApplication::unhandledException() callback. +*/ +#ifndef BEAST_CATCH_UNHANDLED_EXCEPTIONS + //#define BEAST_CATCH_UNHANDLED_EXCEPTIONS 1 +#endif + +//============================================================================= +//============================================================================= +#if BEAST_MSVC + #pragma warning (disable: 4251) // (DLL build warning, must be disabled before pushing the warning state) + #pragma warning (push) + #pragma warning (disable: 4786) // (long class name warning) + #ifdef __INTEL_COMPILER + #pragma warning (disable: 1125) + #endif +#endif + +#include "system/beast_StandardHeader.h" namespace beast { -#include "memory/beast_ScopedPointer.h" +// START_AUTOINCLUDE containers, files, json, logging, maths, memory, misc, network, +// streams, system, text, threads, time, unit_tests, xml, zip +#ifndef BEAST_ABSTRACTFIFO_BEASTHEADER + #include "containers/beast_AbstractFifo.h" +#endif +#ifndef BEAST_ARRAY_BEASTHEADER + #include "containers/beast_Array.h" +#endif +#ifndef BEAST_ARRAYALLOCATIONBASE_BEASTHEADER + #include "containers/beast_ArrayAllocationBase.h" +#endif +#ifndef BEAST_DYNAMICOBJECT_BEASTHEADER + #include "containers/beast_DynamicObject.h" +#endif +#ifndef BEAST_ELEMENTCOMPARATOR_BEASTHEADER + #include "containers/beast_ElementComparator.h" +#endif +#ifndef BEAST_HASHMAP_BEASTHEADER + #include "containers/beast_HashMap.h" +#endif +#ifndef BEAST_LINKEDLISTPOINTER_BEASTHEADER + #include "containers/beast_LinkedListPointer.h" +#endif +#ifndef BEAST_NAMEDVALUESET_BEASTHEADER + #include "containers/beast_NamedValueSet.h" +#endif +#ifndef BEAST_OWNEDARRAY_BEASTHEADER + #include "containers/beast_OwnedArray.h" +#endif +#ifndef BEAST_PROPERTYSET_BEASTHEADER + #include "containers/beast_PropertySet.h" +#endif +#ifndef BEAST_REFERENCECOUNTEDARRAY_BEASTHEADER + #include "containers/beast_ReferenceCountedArray.h" +#endif +#ifndef BEAST_SCOPEDVALUESETTER_BEASTHEADER + #include "containers/beast_ScopedValueSetter.h" +#endif +#ifndef BEAST_SORTEDSET_BEASTHEADER + #include "containers/beast_SortedSet.h" +#endif +#ifndef BEAST_SPARSESET_BEASTHEADER + #include "containers/beast_SparseSet.h" +#endif +#ifndef BEAST_VARIANT_BEASTHEADER + #include "containers/beast_Variant.h" +#endif +#ifndef BEAST_DIRECTORYITERATOR_BEASTHEADER + #include "files/beast_DirectoryIterator.h" +#endif +#ifndef BEAST_FILE_BEASTHEADER + #include "files/beast_File.h" +#endif +#ifndef BEAST_FILEINPUTSTREAM_BEASTHEADER + #include "files/beast_FileInputStream.h" +#endif +#ifndef BEAST_FILEOUTPUTSTREAM_BEASTHEADER + #include "files/beast_FileOutputStream.h" +#endif +#ifndef BEAST_FILESEARCHPATH_BEASTHEADER + #include "files/beast_FileSearchPath.h" +#endif +#ifndef BEAST_MEMORYMAPPEDFILE_BEASTHEADER + #include "files/beast_MemoryMappedFile.h" +#endif +#ifndef BEAST_TEMPORARYFILE_BEASTHEADER + #include "files/beast_TemporaryFile.h" +#endif +#ifndef BEAST_JSON_BEASTHEADER + #include "json/beast_JSON.h" +#endif +#ifndef BEAST_FILELOGGER_BEASTHEADER + #include "logging/beast_FileLogger.h" +#endif +#ifndef BEAST_LOGGER_BEASTHEADER + #include "logging/beast_Logger.h" +#endif +#ifndef BEAST_BIGINTEGER_BEASTHEADER + #include "maths/beast_BigInteger.h" +#endif +#ifndef BEAST_EXPRESSION_BEASTHEADER + #include "maths/beast_Expression.h" +#endif +#ifndef BEAST_MATHSFUNCTIONS_BEASTHEADER + #include "maths/beast_MathsFunctions.h" +#endif +#ifndef BEAST_RANDOM_BEASTHEADER + #include "maths/beast_Random.h" +#endif +#ifndef BEAST_RANGE_BEASTHEADER + #include "maths/beast_Range.h" +#endif +#ifndef BEAST_ATOMIC_BEASTHEADER + #include "memory/beast_Atomic.h" +#endif +#ifndef BEAST_BYTEORDER_BEASTHEADER + #include "memory/beast_ByteOrder.h" +#endif +#ifndef BEAST_HEAPBLOCK_BEASTHEADER + #include "memory/beast_HeapBlock.h" +#endif +#ifndef BEAST_LEAKEDOBJECTDETECTOR_BEASTHEADER + #include "memory/beast_LeakedObjectDetector.h" +#endif +#ifndef BEAST_MEMORY_BEASTHEADER + #include "memory/beast_Memory.h" +#endif +#ifndef BEAST_MEMORYBLOCK_BEASTHEADER + #include "memory/beast_MemoryBlock.h" +#endif +#ifndef BEAST_OPTIONALSCOPEDPOINTER_BEASTHEADER + #include "memory/beast_OptionalScopedPointer.h" +#endif +#ifndef BEAST_REFERENCECOUNTEDOBJECT_BEASTHEADER + #include "memory/beast_ReferenceCountedObject.h" +#endif +#ifndef BEAST_SCOPEDPOINTER_BEASTHEADER + #include "memory/beast_ScopedPointer.h" +#endif +#ifndef BEAST_SINGLETON_BEASTHEADER + #include "memory/beast_Singleton.h" +#endif +#ifndef BEAST_WEAKREFERENCE_BEASTHEADER + #include "memory/beast_WeakReference.h" +#endif +#ifndef BEAST_RESULT_BEASTHEADER + #include "misc/beast_Result.h" +#endif +#ifndef BEAST_UUID_BEASTHEADER + #include "misc/beast_Uuid.h" +#endif +#ifndef BEAST_WINDOWSREGISTRY_BEASTHEADER + #include "misc/beast_WindowsRegistry.h" +#endif +#ifndef BEAST_IPADDRESS_BEASTHEADER + #include "network/beast_IPAddress.h" +#endif +#ifndef BEAST_MACADDRESS_BEASTHEADER + #include "network/beast_MACAddress.h" +#endif +#ifndef BEAST_NAMEDPIPE_BEASTHEADER + #include "network/beast_NamedPipe.h" +#endif +#ifndef BEAST_SOCKET_BEASTHEADER + #include "network/beast_Socket.h" +#endif +#ifndef BEAST_URL_BEASTHEADER + #include "network/beast_URL.h" +#endif +#ifndef BEAST_BUFFEREDINPUTSTREAM_BEASTHEADER + #include "streams/beast_BufferedInputStream.h" +#endif +#ifndef BEAST_FILEINPUTSOURCE_BEASTHEADER + #include "streams/beast_FileInputSource.h" +#endif +#ifndef BEAST_INPUTSOURCE_BEASTHEADER + #include "streams/beast_InputSource.h" +#endif +#ifndef BEAST_INPUTSTREAM_BEASTHEADER + #include "streams/beast_InputStream.h" +#endif +#ifndef BEAST_MEMORYINPUTSTREAM_BEASTHEADER + #include "streams/beast_MemoryInputStream.h" +#endif +#ifndef BEAST_MEMORYOUTPUTSTREAM_BEASTHEADER + #include "streams/beast_MemoryOutputStream.h" +#endif +#ifndef BEAST_OUTPUTSTREAM_BEASTHEADER + #include "streams/beast_OutputStream.h" +#endif +#ifndef BEAST_SUBREGIONSTREAM_BEASTHEADER + #include "streams/beast_SubregionStream.h" +#endif +#ifndef BEAST_PLATFORMDEFS_BEASTHEADER + #include "system/beast_PlatformDefs.h" +#endif +#ifndef BEAST_STANDARDHEADER_BEASTHEADER + #include "system/beast_StandardHeader.h" +#endif +#ifndef BEAST_SYSTEMSTATS_BEASTHEADER + #include "system/beast_SystemStats.h" +#endif +#ifndef BEAST_TARGETPLATFORM_BEASTHEADER + #include "system/beast_TargetPlatform.h" +#endif +#ifndef BEAST_CHARACTERFUNCTIONS_BEASTHEADER + #include "text/beast_CharacterFunctions.h" +#endif +#ifndef BEAST_CHARPOINTER_ASCII_BEASTHEADER + #include "text/beast_CharPointer_ASCII.h" +#endif +#ifndef BEAST_CHARPOINTER_UTF16_BEASTHEADER + #include "text/beast_CharPointer_UTF16.h" +#endif +#ifndef BEAST_CHARPOINTER_UTF32_BEASTHEADER + #include "text/beast_CharPointer_UTF32.h" +#endif +#ifndef BEAST_CHARPOINTER_UTF8_BEASTHEADER + #include "text/beast_CharPointer_UTF8.h" +#endif +#ifndef BEAST_IDENTIFIER_BEASTHEADER + #include "text/beast_Identifier.h" +#endif +#ifndef BEAST_LOCALISEDSTRINGS_BEASTHEADER + #include "text/beast_LocalisedStrings.h" +#endif +#ifndef BEAST_NEWLINE_BEASTHEADER + #include "text/beast_NewLine.h" +#endif +#ifndef BEAST_STRING_BEASTHEADER + #include "text/beast_String.h" +#endif +#ifndef BEAST_STRINGARRAY_BEASTHEADER + #include "text/beast_StringArray.h" +#endif +#ifndef BEAST_STRINGPAIRARRAY_BEASTHEADER + #include "text/beast_StringPairArray.h" +#endif +#ifndef BEAST_STRINGPOOL_BEASTHEADER + #include "text/beast_StringPool.h" +#endif +#ifndef BEAST_TEXTDIFF_BEASTHEADER + #include "text/beast_TextDiff.h" +#endif +#ifndef BEAST_CHILDPROCESS_BEASTHEADER + #include "threads/beast_ChildProcess.h" +#endif +#ifndef BEAST_CRITICALSECTION_BEASTHEADER + #include "threads/beast_CriticalSection.h" +#endif +#ifndef BEAST_DYNAMICLIBRARY_BEASTHEADER + #include "threads/beast_DynamicLibrary.h" +#endif +#ifndef BEAST_HIGHRESOLUTIONTIMER_BEASTHEADER + #include "threads/beast_HighResolutionTimer.h" +#endif +#ifndef BEAST_INTERPROCESSLOCK_BEASTHEADER + #include "threads/beast_InterProcessLock.h" +#endif +#ifndef BEAST_PROCESS_BEASTHEADER + #include "threads/beast_Process.h" +#endif +#ifndef BEAST_READWRITELOCK_BEASTHEADER + #include "threads/beast_ReadWriteLock.h" +#endif +#ifndef BEAST_SCOPEDLOCK_BEASTHEADER + #include "threads/beast_ScopedLock.h" +#endif +#ifndef BEAST_SCOPEDREADLOCK_BEASTHEADER + #include "threads/beast_ScopedReadLock.h" +#endif +#ifndef BEAST_SCOPEDWRITELOCK_BEASTHEADER + #include "threads/beast_ScopedWriteLock.h" +#endif +#ifndef BEAST_SPINLOCK_BEASTHEADER + #include "threads/beast_SpinLock.h" +#endif +#ifndef BEAST_THREAD_BEASTHEADER + #include "threads/beast_Thread.h" +#endif +#ifndef BEAST_THREADLOCALVALUE_BEASTHEADER + #include "threads/beast_ThreadLocalValue.h" +#endif +#ifndef BEAST_THREADPOOL_BEASTHEADER + #include "threads/beast_ThreadPool.h" +#endif +#ifndef BEAST_TIMESLICETHREAD_BEASTHEADER + #include "threads/beast_TimeSliceThread.h" +#endif +#ifndef BEAST_WAITABLEEVENT_BEASTHEADER + #include "threads/beast_WaitableEvent.h" +#endif +#ifndef BEAST_PERFORMANCECOUNTER_BEASTHEADER + #include "time/beast_PerformanceCounter.h" +#endif +#ifndef BEAST_RELATIVETIME_BEASTHEADER + #include "time/beast_RelativeTime.h" +#endif +#ifndef BEAST_TIME_BEASTHEADER + #include "time/beast_Time.h" +#endif +#ifndef BEAST_UNITTEST_BEASTHEADER + #include "unit_tests/beast_UnitTest.h" +#endif +#ifndef BEAST_XMLDOCUMENT_BEASTHEADER + #include "xml/beast_XmlDocument.h" +#endif +#ifndef BEAST_XMLELEMENT_BEASTHEADER + #include "xml/beast_XmlElement.h" +#endif +#ifndef BEAST_GZIPCOMPRESSOROUTPUTSTREAM_BEASTHEADER + #include "zip/beast_GZIPCompressorOutputStream.h" +#endif +#ifndef BEAST_GZIPDECOMPRESSORINPUTSTREAM_BEASTHEADER + #include "zip/beast_GZIPDecompressorInputStream.h" +#endif +#ifndef BEAST_ZIPFILE_BEASTHEADER + #include "zip/beast_ZipFile.h" +#endif +// END_AUTOINCLUDE } + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +#endif // BEAST_CORE_BEASTHEADER diff --git a/modules/beast_core/beast_core.mm b/modules/beast_core/beast_core.mm index 96fbd4d0a8..2b9492c624 100644 --- a/modules/beast_core/beast_core.mm +++ b/modules/beast_core/beast_core.mm @@ -1 +1,24 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + #include "beast_core.cpp" diff --git a/modules/beast_core/containers/beast_AbstractFifo.cpp b/modules/beast_core/containers/beast_AbstractFifo.cpp new file mode 100644 index 0000000000..9663e79f6a --- /dev/null +++ b/modules/beast_core/containers/beast_AbstractFifo.cpp @@ -0,0 +1,229 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +AbstractFifo::AbstractFifo (const int capacity) noexcept + : bufferSize (capacity) +{ + bassert (bufferSize > 0); +} + +AbstractFifo::~AbstractFifo() {} + +int AbstractFifo::getTotalSize() const noexcept { return bufferSize; } +int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady(); } + +int AbstractFifo::getNumReady() const noexcept +{ + const int vs = validStart.get(); + const int ve = validEnd.get(); + return ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); +} + +void AbstractFifo::reset() noexcept +{ + validEnd = 0; + validStart = 0; +} + +void AbstractFifo::setTotalSize (int newSize) noexcept +{ + bassert (newSize > 0); + reset(); + bufferSize = newSize; +} + +//============================================================================== +void AbstractFifo::prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept +{ + const int vs = validStart.get(); + const int ve = validEnd.value; + + const int freeSpace = ve >= vs ? (bufferSize - (ve - vs)) : (vs - ve); + numToWrite = bmin (numToWrite, freeSpace - 1); + + if (numToWrite <= 0) + { + startIndex1 = 0; + startIndex2 = 0; + blockSize1 = 0; + blockSize2 = 0; + } + else + { + startIndex1 = ve; + startIndex2 = 0; + blockSize1 = bmin (bufferSize - ve, numToWrite); + numToWrite -= blockSize1; + blockSize2 = numToWrite <= 0 ? 0 : bmin (numToWrite, vs); + } +} + +void AbstractFifo::finishedWrite (int numWritten) noexcept +{ + bassert (numWritten >= 0 && numWritten < bufferSize); + int newEnd = validEnd.value + numWritten; + if (newEnd >= bufferSize) + newEnd -= bufferSize; + + validEnd = newEnd; +} + +void AbstractFifo::prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept +{ + const int vs = validStart.value; + const int ve = validEnd.get(); + + const int numReady = ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); + numWanted = bmin (numWanted, numReady); + + if (numWanted <= 0) + { + startIndex1 = 0; + startIndex2 = 0; + blockSize1 = 0; + blockSize2 = 0; + } + else + { + startIndex1 = vs; + startIndex2 = 0; + blockSize1 = bmin (bufferSize - vs, numWanted); + numWanted -= blockSize1; + blockSize2 = numWanted <= 0 ? 0 : bmin (numWanted, ve); + } +} + +void AbstractFifo::finishedRead (int numRead) noexcept +{ + bassert (numRead >= 0 && numRead <= bufferSize); + + int newStart = validStart.value + numRead; + if (newStart >= bufferSize) + newStart -= bufferSize; + + validStart = newStart; +} + +//============================================================================== +//============================================================================== +#if BEAST_UNIT_TESTS + +class AbstractFifoTests : public UnitTest +{ +public: + AbstractFifoTests() : UnitTest ("Abstract Fifo") {} + + class WriteThread : public Thread + { + public: + WriteThread (AbstractFifo& fifo_, int* buffer_) + : Thread ("fifo writer"), fifo (fifo_), buffer (buffer_) + { + startThread(); + } + + ~WriteThread() + { + stopThread (5000); + } + + void run() + { + int n = 0; + Random r; + + while (! threadShouldExit()) + { + int num = r.nextInt (2000) + 1; + + int start1, size1, start2, size2; + fifo.prepareToWrite (num, start1, size1, start2, size2); + + bassert (size1 >= 0 && size2 >= 0); + bassert (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())); + bassert (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize())); + + for (int i = 0; i < size1; ++i) + buffer [start1 + i] = n++; + + for (int i = 0; i < size2; ++i) + buffer [start2 + i] = n++; + + fifo.finishedWrite (size1 + size2); + } + } + + private: + AbstractFifo& fifo; + int* buffer; + }; + + void runTest() + { + beginTest ("AbstractFifo"); + + int buffer [5000]; + AbstractFifo fifo (numElementsInArray (buffer)); + + WriteThread writer (fifo, buffer); + + int n = 0; + Random r; + + for (int count = 100000; --count >= 0;) + { + int num = r.nextInt (6000) + 1; + + int start1, size1, start2, size2; + fifo.prepareToRead (num, start1, size1, start2, size2); + + if (! (size1 >= 0 && size2 >= 0) + && (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())) + && (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize()))) + { + expect (false, "prepareToRead returned -ve values"); + break; + } + + bool failed = false; + + for (int i = 0; i < size1; ++i) + failed = (buffer [start1 + i] != n++) || failed; + + for (int i = 0; i < size2; ++i) + failed = (buffer [start2 + i] != n++) || failed; + + if (failed) + { + expect (false, "read values were incorrect"); + break; + } + + fifo.finishedRead (size1 + size2); + } + } +}; + +static AbstractFifoTests fifoUnitTests; + +#endif diff --git a/modules/beast_core/containers/beast_AbstractFifo.h b/modules/beast_core/containers/beast_AbstractFifo.h new file mode 100644 index 0000000000..4c5e6856c8 --- /dev/null +++ b/modules/beast_core/containers/beast_AbstractFifo.h @@ -0,0 +1,217 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ABSTRACTFIFO_BEASTHEADER +#define BEAST_ABSTRACTFIFO_BEASTHEADER + +#include "../memory/beast_Atomic.h" + + +//============================================================================== +/** + Encapsulates the logic required to implement a lock-free FIFO. + + This class handles the logic needed when building a single-reader, single-writer FIFO. + + It doesn't actually hold any data itself, but your FIFO class can use one of these to manage + its position and status when reading or writing to it. + + To use it, you can call prepareToWrite() to determine the position within your own buffer that + an incoming block of data should be stored, and prepareToRead() to find out when the next + outgoing block should be read from. + + e.g. + @code + class MyFifo + { + public: + MyFifo() : abstractFifo (1024) + { + } + + void addToFifo (const int* someData, int numItems) + { + int start1, size1, start2, size2; + abstractFifo.prepareToWrite (numItems, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (myBuffer + start1, someData, size1); + + if (size2 > 0) + copySomeData (myBuffer + start2, someData + size1, size2); + + abstractFifo.finishedWrite (size1 + size2); + } + + void readFromFifo (int* someData, int numItems) + { + int start1, size1, start2, size2; + abstractFifo.prepareToRead (numSamples, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (someData, myBuffer + start1, size1); + + if (size2 > 0) + copySomeData (someData + size1, myBuffer + start2, size2); + + abstractFifo.finishedRead (size1 + size2); + } + + private: + AbstractFifo abstractFifo; + int myBuffer [1024]; + }; + @endcode +*/ +class BEAST_API AbstractFifo +{ +public: + //============================================================================== + /** Creates a FIFO to manage a buffer with the specified capacity. */ + AbstractFifo (int capacity) noexcept; + + /** Destructor */ + ~AbstractFifo(); + + //============================================================================== + /** Returns the total size of the buffer being managed. */ + int getTotalSize() const noexcept; + + /** Returns the number of items that can currently be added to the buffer without it overflowing. */ + int getFreeSpace() const noexcept; + + /** Returns the number of items that can currently be read from the buffer. */ + int getNumReady() const noexcept; + + /** Clears the buffer positions, so that it appears empty. */ + void reset() noexcept; + + /** Changes the buffer's total size. + Note that this isn't thread-safe, so don't call it if there's any danger that it + might overlap with a call to any other method in this class! + */ + void setTotalSize (int newSize) noexcept; + + //============================================================================== + /** Returns the location within the buffer at which an incoming block of data should be written. + + Because the section of data that you want to add to the buffer may overlap the end + and wrap around to the start, two blocks within your buffer are returned, and you + should copy your data into the first one, with any remaining data spilling over into + the second. + + If the number of items you ask for is too large to fit within the buffer's free space, then + blockSize1 + blockSize2 may add up to a lower value than numToWrite. If this happens, you + may decide to keep waiting and re-trying the method until there's enough space available. + + After calling this method, if you choose to write your data into the blocks returned, you + must call finishedWrite() to tell the FIFO how much data you actually added. + + e.g. + @code + void addToFifo (const int* someData, int numItems) + { + int start1, size1, start2, size2; + prepareToWrite (numItems, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (myBuffer + start1, someData, size1); + + if (size2 > 0) + copySomeData (myBuffer + start2, someData + size1, size2); + + finishedWrite (size1 + size2); + } + @endcode + + @param numToWrite indicates how many items you'd like to add to the buffer + @param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written + @param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1 + @param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into + the first block should be written + @param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2 + @see finishedWrite + */ + void prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept; + + /** Called after writing from the FIFO, to indicate that this many items have been added. + @see prepareToWrite + */ + void finishedWrite (int numWritten) noexcept; + + /** Returns the location within the buffer from which the next block of data should be read. + + Because the section of data that you want to read from the buffer may overlap the end + and wrap around to the start, two blocks within your buffer are returned, and you + should read from both of them. + + If the number of items you ask for is greater than the amount of data available, then + blockSize1 + blockSize2 may add up to a lower value than numWanted. If this happens, you + may decide to keep waiting and re-trying the method until there's enough data available. + + After calling this method, if you choose to read the data, you must call finishedRead() to + tell the FIFO how much data you have consumed. + + e.g. + @code + void readFromFifo (int* someData, int numItems) + { + int start1, size1, start2, size2; + prepareToRead (numSamples, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (someData, myBuffer + start1, size1); + + if (size2 > 0) + copySomeData (someData + size1, myBuffer + start2, size2); + + finishedRead (size1 + size2); + } + @endcode + + @param numWanted indicates how many items you'd like to add to the buffer + @param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written + @param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1 + @param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into + the first block should be written + @param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2 + @see finishedRead + */ + void prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept; + + /** Called after reading from the FIFO, to indicate that this many items have now been consumed. + @see prepareToRead + */ + void finishedRead (int numRead) noexcept; + + +private: + //============================================================================== + int bufferSize; + Atomic validStart, validEnd; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) +}; + + +#endif // BEAST_ABSTRACTFIFO_BEASTHEADER diff --git a/modules/beast_core/containers/beast_Array.h b/modules/beast_core/containers/beast_Array.h new file mode 100644 index 0000000000..35c6ac42d0 --- /dev/null +++ b/modules/beast_core/containers/beast_Array.h @@ -0,0 +1,1047 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ARRAY_BEASTHEADER +#define BEAST_ARRAY_BEASTHEADER + +#include "beast_ArrayAllocationBase.h" +#include "beast_ElementComparator.h" +#include "../threads/beast_CriticalSection.h" + + +//============================================================================== +/** + Holds a resizable array of primitive or copy-by-value objects. + + Examples of arrays are: Array, Array or Array + + The Array class can be used to hold simple, non-polymorphic objects as well as primitive types - to + do so, the class must fulfil these requirements: + - it must have a copy constructor and assignment operator + - it must be able to be relocated in memory by a memcpy without this causing any problems - so + objects whose functionality relies on external pointers or references to themselves can be used. + + You can of course have an array of pointers to any kind of object, e.g. Array , but if + you do this, the array doesn't take any ownership of the objects - see the OwnedArray class or the + ReferenceCountedArray class for more powerful ways of holding lists of objects. + + For holding lists of strings, you can use Array\, but it's usually better to use the + specialised class StringArray, which provides more useful functions. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see OwnedArray, ReferenceCountedArray, StringArray, CriticalSection +*/ +template +class Array +{ +private: + typedef PARAMETER_TYPE (ElementType) ParameterType; + +public: + //============================================================================== + /** Creates an empty array. */ + Array() noexcept + : numUsed (0) + { + } + + /** Creates a copy of another array. + @param other the array to copy + */ + Array (const Array& other) + { + const ScopedLockType lock (other.getLock()); + numUsed = other.numUsed; + data.setAllocatedSize (other.numUsed); + + for (int i = 0; i < numUsed; ++i) + new (data.elements + i) ElementType (other.data.elements[i]); + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + Array (Array&& other) noexcept + : data (static_cast &&> (other.data)), + numUsed (other.numUsed) + { + other.numUsed = 0; + } + #endif + + /** Initalises from a null-terminated C array of values. + + @param values the array to copy from + */ + template + explicit Array (const TypeToCreateFrom* values) + : numUsed (0) + { + while (*values != TypeToCreateFrom()) + add (*values++); + } + + /** Initalises from a C array of values. + + @param values the array to copy from + @param numValues the number of values in the array + */ + template + Array (const TypeToCreateFrom* values, int numValues) + : numUsed (numValues) + { + data.setAllocatedSize (numValues); + + for (int i = 0; i < numValues; ++i) + new (data.elements + i) ElementType (values[i]); + } + + /** Destructor. */ + ~Array() + { + deleteAllElements(); + } + + /** Copies another array. + @param other the array to copy + */ + Array& operator= (const Array& other) + { + if (this != &other) + { + Array otherCopy (other); + swapWithArray (otherCopy); + } + + return *this; + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + Array& operator= (Array&& other) noexcept + { + const ScopedLockType lock (getLock()); + data = static_cast &&> (other.data); + numUsed = other.numUsed; + other.numUsed = 0; + return *this; + } + #endif + + //============================================================================== + /** Compares this array to another one. + Two arrays are considered equal if they both contain the same set of + elements, in the same order. + @param other the other array to compare with + */ + template + bool operator== (const OtherArrayType& other) const + { + const ScopedLockType lock (getLock()); + const typename OtherArrayType::ScopedLockType lock2 (other.getLock()); + + if (numUsed != other.numUsed) + return false; + + for (int i = numUsed; --i >= 0;) + if (! (data.elements [i] == other.data.elements [i])) + return false; + + return true; + } + + /** Compares this array to another one. + Two arrays are considered equal if they both contain the same set of + elements, in the same order. + @param other the other array to compare with + */ + template + bool operator!= (const OtherArrayType& other) const + { + return ! operator== (other); + } + + //============================================================================== + /** Removes all elements from the array. + This will remove all the elements, and free any storage that the array is + using. To clear the array without freeing the storage, use the clearQuick() + method instead. + + @see clearQuick + */ + void clear() + { + const ScopedLockType lock (getLock()); + deleteAllElements(); + data.setAllocatedSize (0); + numUsed = 0; + } + + /** Removes all elements from the array without freeing the array's allocated storage. + + @see clear + */ + void clearQuick() + { + const ScopedLockType lock (getLock()); + deleteAllElements(); + numUsed = 0; + } + + //============================================================================== + /** Returns the current number of elements in the array. + */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns one of the elements in the array. + If the index passed in is beyond the range of valid elements, this + will return a default value. + + If you're certain that the index will always be a valid element, you + can call getUnchecked() instead, which is faster. + + @param index the index of the element being requested (0 is the first element in the array) + @see getUnchecked, getFirst, getLast + */ + ElementType operator[] (const int index) const + { + const ScopedLockType lock (getLock()); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : ElementType(); + } + + /** Returns one of the elements in the array, without checking the index passed in. + + Unlike the operator[] method, this will try to return an element without + checking that the index is within the bounds of the array, so should only + be used when you're confident that it will always be a valid index. + + @param index the index of the element being requested (0 is the first element in the array) + @see operator[], getFirst, getLast + */ + inline ElementType getUnchecked (const int index) const + { + const ScopedLockType lock (getLock()); + bassert (isPositiveAndBelow (index, numUsed)); + return data.elements [index]; + } + + /** Returns a direct reference to one of the elements in the array, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + @see operator[], getFirst, getLast + */ + inline ElementType& getReference (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + bassert (isPositiveAndBelow (index, numUsed)); + return data.elements [index]; + } + + /** Returns the first element in the array, or a default value if the array is empty. + + @see operator[], getUnchecked, getLast + */ + inline ElementType getFirst() const + { + const ScopedLockType lock (getLock()); + return (numUsed > 0) ? data.elements [0] + : ElementType(); + } + + /** Returns the last element in the array, or a default value if the array is empty. + + @see operator[], getUnchecked, getFirst + */ + inline ElementType getLast() const + { + const ScopedLockType lock (getLock()); + return (numUsed > 0) ? data.elements [numUsed - 1] + : ElementType(); + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ElementType* getRawDataPointer() noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* end() const noexcept + { + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of the first element which matches the value passed in. + + This will search the array for the given object, and return the index + of its first occurrence. If the object isn't found, the method will return -1. + + @param elementToLookFor the value or object to look for + @returns the index of the object, or -1 if it's not found + */ + int indexOf (ParameterType elementToLookFor) const + { + const ScopedLockType lock (getLock()); + const ElementType* e = data.elements.getData(); + const ElementType* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (elementToLookFor == *e) + return static_cast (e - data.elements.getData()); + + return -1; + } + + /** Returns true if the array contains at least one occurrence of an object. + + @param elementToLookFor the value or object to look for + @returns true if the item is found + */ + bool contains (ParameterType elementToLookFor) const + { + const ScopedLockType lock (getLock()); + const ElementType* e = data.elements.getData(); + const ElementType* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (elementToLookFor == *e) + return true; + + return false; + } + + //============================================================================== + /** Appends a new element at the end of the array. + + @param newElement the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addUsingDefaultSort, addArray + */ + void add (ParameterType newElement) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (newElement); + } + + /** Inserts a new element into the array at a given position. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the new element should be + inserted (pass in -1 to add it to the end) + @param newElement the new object to add to the array + @see add, addSorted, addUsingDefaultSort, set + */ + void insert (int indexToInsertAt, ParameterType newElement) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + ElementType* const insertPos = data.elements + indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + + if (numberToMove > 0) + memmove (insertPos + 1, insertPos, ((size_t) numberToMove) * sizeof (ElementType)); + + new (insertPos) ElementType (newElement); + ++numUsed; + } + else + { + new (data.elements + numUsed++) ElementType (newElement); + } + } + + /** Inserts multiple copies of an element into the array at a given position. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the new element should be inserted + @param newElement the new object to add to the array + @param numberOfTimesToInsertIt how many copies of the value to insert + @see insert, add, addSorted, set + */ + void insertMultiple (int indexToInsertAt, ParameterType newElement, + int numberOfTimesToInsertIt) + { + if (numberOfTimesToInsertIt > 0) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + numberOfTimesToInsertIt); + ElementType* insertPos; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos = data.elements + indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + memmove (insertPos + numberOfTimesToInsertIt, insertPos, ((size_t) numberToMove) * sizeof (ElementType)); + } + else + { + insertPos = data.elements + numUsed; + } + + numUsed += numberOfTimesToInsertIt; + + while (--numberOfTimesToInsertIt >= 0) + new (insertPos++) ElementType (newElement); + } + } + + /** Inserts an array of values into this array at a given position. + + If the index is less than 0 or greater than the size of the array, the + new elements will be added to the end of the array. + Otherwise, they will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the first new element should be inserted + @param newElements the new values to add to the array + @param numberOfElements how many items are in the array + @see insert, add, addSorted, set + */ + void insertArray (int indexToInsertAt, + const ElementType* newElements, + int numberOfElements) + { + if (numberOfElements > 0) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + numberOfElements); + ElementType* insertPos = data.elements; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos += indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ElementType)); + } + else + { + insertPos += numUsed; + } + + numUsed += numberOfElements; + + while (--numberOfElements >= 0) + new (insertPos++) ElementType (*newElements++); + } + } + + /** Appends a new element at the end of the array as long as the array doesn't + already contain it. + + If the array already contains an element that matches the one passed in, nothing + will be done. + + @param newElement the new object to add to the array + */ + void addIfNotAlreadyThere (ParameterType newElement) + { + const ScopedLockType lock (getLock()); + + if (! contains (newElement)) + add (newElement); + } + + /** Replaces an element with a new value. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the item is added to the end of the array. + + @param indexToChange the index whose value you want to change + @param newValue the new value to set for this index. + @see add, insert + */ + void set (const int indexToChange, ParameterType newValue) + { + bassert (indexToChange >= 0); + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToChange, numUsed)) + { + data.elements [indexToChange] = newValue; + } + else if (indexToChange >= 0) + { + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (newValue); + } + } + + /** Replaces an element with a new value without doing any bounds-checking. + + This just sets a value directly in the array's internal storage, so you'd + better make sure it's in range! + + @param indexToChange the index whose value you want to change + @param newValue the new value to set for this index. + @see set, getUnchecked + */ + void setUnchecked (const int indexToChange, ParameterType newValue) + { + const ScopedLockType lock (getLock()); + bassert (isPositiveAndBelow (indexToChange, numUsed)); + data.elements [indexToChange] = newValue; + } + + /** Adds elements from an array to the end of this array. + + @param elementsToAdd the array of elements to add + @param numElementsToAdd how many elements are in this other array + @see add + */ + void addArray (const ElementType* elementsToAdd, int numElementsToAdd) + { + const ScopedLockType lock (getLock()); + + if (numElementsToAdd > 0) + { + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + { + new (data.elements + numUsed) ElementType (*elementsToAdd++); + ++numUsed; + } + } + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + void swapWithArray (Array& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const ScopedLockType lock2 (otherArray.getLock()); + + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addArray (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); + + { + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + while (--numElementsToAdd >= 0) + add (arrayToAddFrom.getUnchecked (startIndex++)); + } + } + + /** This will enlarge or shrink the array to the given number of elements, by adding + or removing items from its end. + + If the array is smaller than the given target size, empty elements will be appended + until its size is as specified. If its size is larger than the target, items will be + removed from its end to shorten it. + */ + void resize (const int targetNumItems) + { + bassert (targetNumItems >= 0); + + const int numToAdd = targetNumItems - numUsed; + if (numToAdd > 0) + insertMultiple (numUsed, ElementType(), numToAdd); + else if (numToAdd < 0) + removeRange (targetNumItems, -numToAdd); + } + + /** Inserts a new element into the array, assuming that the array is sorted. + + This will use a comparator to find the position at which the new element + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param newElement the new element to insert to the array + @returns the index at which the new item was added + @see addUsingDefaultSort, add, sort + */ + template + int addSorted (ElementComparator& comparator, ParameterType newElement) + { + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newElement, 0, numUsed); + insert (index, newElement); + return index; + } + + /** Inserts a new element into the array, assuming that the array is sorted. + + This will use the DefaultElementComparator class for sorting, so your ElementType + must be suitable for use with that class. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param newElement the new element to insert to the array + @see addSorted, sort + */ + void addUsingDefaultSort (ParameterType newElement) + { + DefaultElementComparator comparator; + addSorted (comparator, newElement); + } + + /** Finds the index of an element in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param elementToLookFor the element to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, TargetValueType elementToLookFor) const + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + const ScopedLockType lock (getLock()); + + for (int s = 0, e = numUsed;;) + { + if (s >= e) + return -1; + + if (comparator.compareElements (elementToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + return -1; + + if (comparator.compareElements (elementToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + } + + //============================================================================== + /** Removes an element from the array. + + This will remove the element at a given index, and move back + all the subsequent elements to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @returns the element that has been removed + @see removeValue, removeRange + */ + ElementType remove (const int indexToRemove) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ElementType removed (data.elements[indexToRemove]); + removeInternal (indexToRemove); + return removed; + } + + return ElementType(); + } + + /** Removes an item from the array. + + This will remove the first occurrence of the given element from the array. + If the item isn't found, no action is taken. + + @param valueToRemove the object to try to remove + @see remove, removeRange + */ + void removeFirstMatchingValue (ParameterType valueToRemove) + { + const ScopedLockType lock (getLock()); + ElementType* const e = data.elements; + + for (int i = 0; i < numUsed; ++i) + { + if (valueToRemove == e[i]) + { + removeInternal (i); + break; + } + } + } + + /** Removes an item from the array. + + This will remove the first occurrence of the given element from the array. + If the item isn't found, no action is taken. + + @param valueToRemove the object to try to remove + @see remove, removeRange + */ + void removeAllInstancesOf (ParameterType valueToRemove) + { + const ScopedLockType lock (getLock()); + + for (int i = numUsed; --i >= 0;) + if (valueToRemove == data.elements[i]) + removeInternal (i); + } + + /** Removes a range of elements from the array. + + This will remove a set of elements, starting from the given index, + and move subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first element to remove + @param numberToRemove how many elements should be removed + @see remove, removeValue + */ + void removeRange (int startIndex, int numberToRemove) + { + const ScopedLockType lock (getLock()); + const int endIndex = blimit (0, numUsed, startIndex + numberToRemove); + startIndex = blimit (0, numUsed, startIndex); + + if (endIndex > startIndex) + { + ElementType* const e = data.elements + startIndex; + + numberToRemove = endIndex - startIndex; + for (int i = 0; i < numberToRemove; ++i) + e[i].~ElementType(); + + const int numToShift = numUsed - endIndex; + if (numToShift > 0) + memmove (e, e + numberToRemove, ((size_t) numToShift) * sizeof (ElementType)); + + numUsed -= numberToRemove; + minimiseStorageAfterRemoval(); + } + } + + /** Removes the last n elements from the array. + + @param howManyToRemove how many elements to remove from the end of the array + @see remove, removeValue, removeRange + */ + void removeLast (int howManyToRemove = 1) + { + const ScopedLockType lock (getLock()); + + if (howManyToRemove > numUsed) + howManyToRemove = numUsed; + + for (int i = 1; i <= howManyToRemove; ++i) + data.elements [numUsed - i].~ElementType(); + + numUsed -= howManyToRemove; + minimiseStorageAfterRemoval(); + } + + /** Removes any elements which are also in another array. + + @param otherArray the other array in which to look for elements to remove + @see removeValuesNotIn, remove, removeValue, removeRange + */ + template + void removeValuesIn (const OtherArrayType& otherArray) + { + const typename OtherArrayType::ScopedLockType lock1 (otherArray.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this == &otherArray) + { + clear(); + } + else + { + if (otherArray.size() > 0) + { + for (int i = numUsed; --i >= 0;) + if (otherArray.contains (data.elements [i])) + removeInternal (i); + } + } + } + + /** Removes any elements which are not found in another array. + + Only elements which occur in this other array will be retained. + + @param otherArray the array in which to look for elements NOT to remove + @see removeValuesIn, remove, removeValue, removeRange + */ + template + void removeValuesNotIn (const OtherArrayType& otherArray) + { + const typename OtherArrayType::ScopedLockType lock1 (otherArray.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this != &otherArray) + { + if (otherArray.size() <= 0) + { + clear(); + } + else + { + for (int i = numUsed; --i >= 0;) + if (! otherArray.contains (data.elements [i])) + removeInternal (i); + } + } + } + + /** Swaps over two elements in the array. + + This swaps over the elements found at the two indexes passed in. + If either index is out-of-range, this method will do nothing. + + @param index1 index of one of the elements to swap + @param index2 index of the other element to swap + */ + void swap (const int index1, + const int index2) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the values to a different position. + + This will move the value to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the value to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this value to end up. If this + is less than zero, the value will be moved to the end + of the array + */ + void move (const int currentIndex, int newIndex) noexcept + { + if (currentIndex != newIndex) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + char tempCopy [sizeof (ElementType)]; + memcpy (tempCopy, data.elements + currentIndex, sizeof (ElementType)); + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ElementType) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ElementType) * (size_t) (currentIndex - newIndex)); + } + + memcpy (data.elements + newIndex, tempCopy, sizeof (ElementType)); + } + } + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() + { + const ScopedLockType lock (getLock()); + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + + @see addSorted, indexOfSorted, sortArray + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) const + { + const ScopedLockType lock (getLock()); + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void removeInternal (const int indexToRemove) + { + --numUsed; + ElementType* const e = data.elements + indexToRemove; + e->~ElementType(); + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, ((size_t) numberToShift) * sizeof (ElementType)); + + minimiseStorageAfterRemoval(); + } + + inline void deleteAllElements() noexcept + { + for (int i = 0; i < numUsed; ++i) + data.elements[i].~ElementType(); + } + + void minimiseStorageAfterRemoval() + { + if (data.numAllocated > bmax (minimumAllocatedSize, numUsed * 2)) + data.shrinkToNoMoreThan (bmax (numUsed, bmax (minimumAllocatedSize, 64 / (int) sizeof (ElementType)))); + } +}; + + +#endif // BEAST_ARRAY_BEASTHEADER diff --git a/modules/beast_core/containers/beast_ArrayAllocationBase.h b/modules/beast_core/containers/beast_ArrayAllocationBase.h new file mode 100644 index 0000000000..479fb9091f --- /dev/null +++ b/modules/beast_core/containers/beast_ArrayAllocationBase.h @@ -0,0 +1,133 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ARRAYALLOCATIONBASE_BEASTHEADER +#define BEAST_ARRAYALLOCATIONBASE_BEASTHEADER + +#include "../memory/beast_HeapBlock.h" + + +//============================================================================== +/** + Implements some basic array storage allocation functions. + + This class isn't really for public use - it's used by the other + array classes, but might come in handy for some purposes. + + It inherits from a critical section class to allow the arrays to use + the "empty base class optimisation" pattern to reduce their footprint. + + @see Array, OwnedArray, ReferenceCountedArray +*/ +template +class ArrayAllocationBase : public TypeOfCriticalSectionToUse +{ +public: + //============================================================================== + /** Creates an empty array. */ + ArrayAllocationBase() noexcept + : numAllocated (0) + { + } + + /** Destructor. */ + ~ArrayAllocationBase() noexcept + { + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + ArrayAllocationBase (ArrayAllocationBase&& other) noexcept + : elements (static_cast &&> (other.elements)), + numAllocated (other.numAllocated) + { + } + + ArrayAllocationBase& operator= (ArrayAllocationBase&& other) noexcept + { + elements = static_cast &&> (other.elements); + numAllocated = other.numAllocated; + return *this; + } + #endif + + //============================================================================== + /** Changes the amount of storage allocated. + + This will retain any data currently held in the array, and either add or + remove extra space at the end. + + @param numElements the number of elements that are needed + */ + void setAllocatedSize (const int numElements) + { + if (numAllocated != numElements) + { + if (numElements > 0) + elements.realloc ((size_t) numElements); + else + elements.free(); + + numAllocated = numElements; + } + } + + /** Increases the amount of storage allocated if it is less than a given amount. + + This will retain any data currently held in the array, but will add + extra space at the end to make sure there it's at least as big as the size + passed in. If it's already bigger, no action is taken. + + @param minNumElements the minimum number of elements that are needed + */ + void ensureAllocatedSize (const int minNumElements) + { + if (minNumElements > numAllocated) + setAllocatedSize ((minNumElements + minNumElements / 2 + 8) & ~7); + } + + /** Minimises the amount of storage allocated so that it's no more than + the given number of elements. + */ + void shrinkToNoMoreThan (const int maxNumElements) + { + if (maxNumElements < numAllocated) + setAllocatedSize (maxNumElements); + } + + /** Swap the contents of two objects. */ + void swapWith (ArrayAllocationBase & other) noexcept + { + elements.swapWith (other.elements); + std::swap (numAllocated, other.numAllocated); + } + + //============================================================================== + HeapBlock elements; + int numAllocated; + +private: + BEAST_DECLARE_NON_COPYABLE (ArrayAllocationBase) +}; + + +#endif // BEAST_ARRAYALLOCATIONBASE_BEASTHEADER diff --git a/modules/beast_core/containers/beast_DynamicObject.cpp b/modules/beast_core/containers/beast_DynamicObject.cpp new file mode 100644 index 0000000000..0a5e15462d --- /dev/null +++ b/modules/beast_core/containers/beast_DynamicObject.cpp @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +DynamicObject::DynamicObject() +{ +} + +DynamicObject::~DynamicObject() +{ +} + +bool DynamicObject::hasProperty (const Identifier& propertyName) const +{ + const var* const v = properties.getVarPointer (propertyName); + return v != nullptr && ! v->isMethod(); +} + +var DynamicObject::getProperty (const Identifier& propertyName) const +{ + return properties [propertyName]; +} + +void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue) +{ + properties.set (propertyName, newValue); +} + +void DynamicObject::removeProperty (const Identifier& propertyName) +{ + properties.remove (propertyName); +} + +bool DynamicObject::hasMethod (const Identifier& methodName) const +{ + return getProperty (methodName).isMethod(); +} + +var DynamicObject::invokeMethod (const Identifier& methodName, + const var* parameters, + int numParameters) +{ + return properties [methodName].invokeMethod (this, parameters, numParameters); +} + +void DynamicObject::setMethod (const Identifier& name, + var::MethodFunction methodFunction) +{ + properties.set (name, var (methodFunction)); +} + +void DynamicObject::clear() +{ + properties.clear(); +} diff --git a/modules/beast_core/containers/beast_DynamicObject.h b/modules/beast_core/containers/beast_DynamicObject.h new file mode 100644 index 0000000000..08ce6d21b7 --- /dev/null +++ b/modules/beast_core/containers/beast_DynamicObject.h @@ -0,0 +1,121 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_DYNAMICOBJECT_BEASTHEADER +#define BEAST_DYNAMICOBJECT_BEASTHEADER + +#include "beast_NamedValueSet.h" +#include "../memory/beast_ReferenceCountedObject.h" + + +//============================================================================== +/** + Represents a dynamically implemented object. + + This class is primarily intended for wrapping scripting language objects, + but could be used for other purposes. + + An instance of a DynamicObject can be used to store named properties, and + by subclassing hasMethod() and invokeMethod(), you can give your object + methods. +*/ +class BEAST_API DynamicObject : public ReferenceCountedObject +{ +public: + //============================================================================== + DynamicObject(); + + /** Destructor. */ + virtual ~DynamicObject(); + + typedef ReferenceCountedObjectPtr Ptr; + + //============================================================================== + /** Returns true if the object has a property with this name. + Note that if the property is actually a method, this will return false. + */ + virtual bool hasProperty (const Identifier& propertyName) const; + + /** Returns a named property. + This returns a void if no such property exists. + */ + virtual var getProperty (const Identifier& propertyName) const; + + /** Sets a named property. */ + virtual void setProperty (const Identifier& propertyName, const var& newValue); + + /** Removes a named property. */ + virtual void removeProperty (const Identifier& propertyName); + + //============================================================================== + /** Checks whether this object has the specified method. + + The default implementation of this just checks whether there's a property + with this name that's actually a method, but this can be overridden for + building objects with dynamic invocation. + */ + virtual bool hasMethod (const Identifier& methodName) const; + + /** Invokes a named method on this object. + + The default implementation looks up the named property, and if it's a method + call, then it invokes it. + + This method is virtual to allow more dynamic invocation to used for objects + where the methods may not already be set as properies. + */ + virtual var invokeMethod (const Identifier& methodName, + const var* parameters, + int numParameters); + + /** Sets up a method. + + This is basically the same as calling setProperty (methodName, (var::MethodFunction) myFunction), but + helps to avoid accidentally invoking the wrong type of var constructor. It also makes + the code easier to read, + + The compiler will probably force you to use an explicit cast your method to a (var::MethodFunction), e.g. + @code + setMethod ("doSomething", (var::MethodFunction) &MyClass::doSomething); + @endcode + */ + void setMethod (const Identifier& methodName, + var::MethodFunction methodFunction); + + //============================================================================== + /** Removes all properties and methods from the object. */ + void clear(); + + /** Returns the NamedValueSet that holds the object's properties. */ + NamedValueSet& getProperties() noexcept { return properties; } + +private: + //============================================================================== + NamedValueSet properties; + + BEAST_LEAK_DETECTOR (DynamicObject) +}; + + + +#endif // BEAST_DYNAMICOBJECT_BEASTHEADER diff --git a/modules/beast_core/containers/beast_ElementComparator.h b/modules/beast_core/containers/beast_ElementComparator.h new file mode 100644 index 0000000000..7ad71b3ea7 --- /dev/null +++ b/modules/beast_core/containers/beast_ElementComparator.h @@ -0,0 +1,274 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ELEMENTCOMPARATOR_BEASTHEADER +#define BEAST_ELEMENTCOMPARATOR_BEASTHEADER + + +//============================================================================== +/** + Sorts a range of elements in an array. + + The comparator object that is passed-in must define a public method with the following + signature: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator an object which defines a compareElements() method + @param array the array to sort + @param firstElement the index of the first element of the range to be sorted + @param lastElement the index of the last element in the range that needs + sorting (this is inclusive) + @param retainOrderOfEquivalentItems if true, the order of items that the + comparator deems the same will be maintained - this will be + a slower algorithm than if they are allowed to be moved around. + + @see sortArrayRetainingOrder +*/ +template +static void sortArray (ElementComparator& comparator, + ElementType* const array, + int firstElement, + int lastElement, + const bool retainOrderOfEquivalentItems) +{ + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + if (lastElement > firstElement) + { + if (retainOrderOfEquivalentItems) + { + for (int i = firstElement; i < lastElement; ++i) + { + if (comparator.compareElements (array[i], array [i + 1]) > 0) + { + std::swap (array[i], array[i + 1]); + + if (i > firstElement) + i -= 2; + } + } + } + else + { + int fromStack[30], toStack[30]; + int stackIndex = 0; + + for (;;) + { + const int size = (lastElement - firstElement) + 1; + + if (size <= 8) + { + int j = lastElement; + int maxIndex; + + while (j > firstElement) + { + maxIndex = firstElement; + for (int k = firstElement + 1; k <= j; ++k) + if (comparator.compareElements (array[k], array [maxIndex]) > 0) + maxIndex = k; + + std::swap (array[j], array[maxIndex]); + --j; + } + } + else + { + const int mid = firstElement + (size >> 1); + std::swap (array[mid], array[firstElement]); + + int i = firstElement; + int j = lastElement + 1; + + for (;;) + { + while (++i <= lastElement + && comparator.compareElements (array[i], array [firstElement]) <= 0) + {} + + while (--j > firstElement + && comparator.compareElements (array[j], array [firstElement]) >= 0) + {} + + if (j < i) + break; + + std::swap (array[i], array[j]); + } + + std::swap (array[j], array[firstElement]); + + if (j - 1 - firstElement >= lastElement - i) + { + if (firstElement + 1 < j) + { + fromStack [stackIndex] = firstElement; + toStack [stackIndex] = j - 1; + ++stackIndex; + } + + if (i < lastElement) + { + firstElement = i; + continue; + } + } + else + { + if (i < lastElement) + { + fromStack [stackIndex] = i; + toStack [stackIndex] = lastElement; + ++stackIndex; + } + + if (firstElement + 1 < j) + { + lastElement = j - 1; + continue; + } + } + } + + if (--stackIndex < 0) + break; + + bassert (stackIndex < numElementsInArray (fromStack)); + + firstElement = fromStack [stackIndex]; + lastElement = toStack [stackIndex]; + } + } + } +} + + +//============================================================================== +/** + Searches a sorted array of elements, looking for the index at which a specified value + should be inserted for it to be in the correct order. + + The comparator object that is passed-in must define a public method with the following + signature: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator an object which defines a compareElements() method + @param array the array to search + @param newElement the value that is going to be inserted + @param firstElement the index of the first element to search + @param lastElement the index of the last element in the range (this is non-inclusive) +*/ +template +static int findInsertIndexInSortedArray (ElementComparator& comparator, + ElementType* const array, + const ElementType newElement, + int firstElement, + int lastElement) +{ + bassert (firstElement <= lastElement); + + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + while (firstElement < lastElement) + { + if (comparator.compareElements (newElement, array [firstElement]) == 0) + { + ++firstElement; + break; + } + else + { + const int halfway = (firstElement + lastElement) >> 1; + + if (halfway == firstElement) + { + if (comparator.compareElements (newElement, array [halfway]) >= 0) + ++firstElement; + + break; + } + else if (comparator.compareElements (newElement, array [halfway]) >= 0) + { + firstElement = halfway; + } + else + { + lastElement = halfway; + } + } + } + + return firstElement; +} + +//============================================================================== +/** + A simple ElementComparator class that can be used to sort an array of + objects that support the '<' operator. + + This will work for primitive types and objects that implement operator<(). + + Example: @code + Array myArray; + DefaultElementComparator sorter; + myArray.sort (sorter); + @endcode + + @see ElementComparator +*/ +template +class DefaultElementComparator +{ +private: + typedef PARAMETER_TYPE (ElementType) ParameterType; + +public: + static int compareElements (ParameterType first, ParameterType second) + { + return (first < second) ? -1 : ((second < first) ? 1 : 0); + } +}; + + +#endif // BEAST_ELEMENTCOMPARATOR_BEASTHEADER diff --git a/modules/beast_core/containers/beast_HashMap.h b/modules/beast_core/containers/beast_HashMap.h new file mode 100644 index 0000000000..d488020bcc --- /dev/null +++ b/modules/beast_core/containers/beast_HashMap.h @@ -0,0 +1,447 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_HASHMAP_BEASTHEADER +#define BEAST_HASHMAP_BEASTHEADER + +#include "beast_OwnedArray.h" +#include "beast_LinkedListPointer.h" +#include "../memory/beast_ScopedPointer.h" + + +//============================================================================== +/** + A simple class to generate hash functions for some primitive types, intended for + use with the HashMap class. + @see HashMap +*/ +class DefaultHashFunctions +{ +public: + /** Generates a simple hash from an integer. */ + static int generateHash (const int key, const int upperLimit) noexcept { return std::abs (key) % upperLimit; } + /** Generates a simple hash from an int64. */ + static int generateHash (const int64 key, const int upperLimit) noexcept { return std::abs ((int) key) % upperLimit; } + /** Generates a simple hash from a string. */ + static int generateHash (const String& key, const int upperLimit) noexcept { return (int) (((uint32) key.hashCode()) % (uint32) upperLimit); } + /** Generates a simple hash from a variant. */ + static int generateHash (const var& key, const int upperLimit) noexcept { return generateHash (key.toString(), upperLimit); } +}; + + +//============================================================================== +/** + Holds a set of mappings between some key/value pairs. + + The types of the key and value objects are set as template parameters. + You can also specify a class to supply a hash function that converts a key value + into an hashed integer. This class must have the form: + + @code + struct MyHashGenerator + { + static int generateHash (MyKeyType key, int upperLimit) + { + // The function must return a value 0 <= x < upperLimit + return someFunctionOfMyKeyType (key) % upperLimit; + } + }; + @endcode + + Like the Array class, the key and value types are expected to be copy-by-value types, so + if you define them to be pointer types, this class won't delete the objects that they + point to. + + If you don't supply a class for the HashFunctionToUse template parameter, the + default one provides some simple mappings for strings and ints. + + @code + HashMap hash; + hash.set (1, "item1"); + hash.set (2, "item2"); + + DBG (hash [1]); // prints "item1" + DBG (hash [2]); // prints "item2" + + // This iterates the map, printing all of its key -> value pairs.. + for (HashMap::Iterator i (hash); i.next();) + DBG (i.getKey() << " -> " << i.getValue()); + @endcode + + @see CriticalSection, DefaultHashFunctions, NamedValueSet, SortedSet +*/ +template +class HashMap +{ +private: + typedef PARAMETER_TYPE (KeyType) KeyTypeParameter; + typedef PARAMETER_TYPE (ValueType) ValueTypeParameter; + +public: + //============================================================================== + /** Creates an empty hash-map. + + The numberOfSlots parameter specifies the number of hash entries the map will use. This + will be the "upperLimit" parameter that is passed to your generateHash() function. The number + of hash slots will grow automatically if necessary, or it can be remapped manually using remapTable(). + */ + explicit HashMap (const int numberOfSlots = defaultHashTableSize) + : totalNumItems (0) + { + slots.insertMultiple (0, nullptr, numberOfSlots); + } + + /** Destructor. */ + ~HashMap() + { + clear(); + } + + //============================================================================== + /** Removes all values from the map. + Note that this will clear the content, but won't affect the number of slots (see + remapTable and getNumSlots). + */ + void clear() + { + const ScopedLockType sl (getLock()); + + for (int i = slots.size(); --i >= 0;) + { + HashEntry* h = slots.getUnchecked(i); + + while (h != nullptr) + { + const ScopedPointer deleter (h); + h = h->nextEntry; + } + + slots.set (i, nullptr); + } + + totalNumItems = 0; + } + + //============================================================================== + /** Returns the current number of items in the map. */ + inline int size() const noexcept + { + return totalNumItems; + } + + /** Returns the value corresponding to a given key. + If the map doesn't contain the key, a default instance of the value type is returned. + @param keyToLookFor the key of the item being requested + */ + inline ValueType operator[] (KeyTypeParameter keyToLookFor) const + { + const ScopedLockType sl (getLock()); + + for (const HashEntry* entry = slots.getUnchecked (generateHashFor (keyToLookFor)); entry != nullptr; entry = entry->nextEntry) + if (entry->key == keyToLookFor) + return entry->value; + + return ValueType(); + } + + //============================================================================== + /** Returns true if the map contains an item with the specied key. */ + bool contains (KeyTypeParameter keyToLookFor) const + { + const ScopedLockType sl (getLock()); + + for (const HashEntry* entry = slots.getUnchecked (generateHashFor (keyToLookFor)); entry != nullptr; entry = entry->nextEntry) + if (entry->key == keyToLookFor) + return true; + + return false; + } + + /** Returns true if the hash contains at least one occurrence of a given value. */ + bool containsValue (ValueTypeParameter valueToLookFor) const + { + const ScopedLockType sl (getLock()); + + for (int i = getNumSlots(); --i >= 0;) + for (const HashEntry* entry = slots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry) + if (entry->value == valueToLookFor) + return true; + + return false; + } + + //============================================================================== + /** Adds or replaces an element in the hash-map. + If there's already an item with the given key, this will replace its value. Otherwise, a new item + will be added to the map. + */ + void set (KeyTypeParameter newKey, ValueTypeParameter newValue) + { + const ScopedLockType sl (getLock()); + const int hashIndex = generateHashFor (newKey); + + HashEntry* const firstEntry = slots.getUnchecked (hashIndex); + + for (HashEntry* entry = firstEntry; entry != nullptr; entry = entry->nextEntry) + { + if (entry->key == newKey) + { + entry->value = newValue; + return; + } + } + + slots.set (hashIndex, new HashEntry (newKey, newValue, firstEntry)); + ++totalNumItems; + + if (totalNumItems > (getNumSlots() * 3) / 2) + remapTable (getNumSlots() * 2); + } + + /** Removes an item with the given key. */ + void remove (KeyTypeParameter keyToRemove) + { + const ScopedLockType sl (getLock()); + const int hashIndex = generateHashFor (keyToRemove); + HashEntry* entry = slots.getUnchecked (hashIndex); + HashEntry* previous = nullptr; + + while (entry != nullptr) + { + if (entry->key == keyToRemove) + { + const ScopedPointer deleter (entry); + + entry = entry->nextEntry; + + if (previous != nullptr) + previous->nextEntry = entry; + else + slots.set (hashIndex, entry); + + --totalNumItems; + } + else + { + previous = entry; + entry = entry->nextEntry; + } + } + } + + /** Removes all items with the given value. */ + void removeValue (ValueTypeParameter valueToRemove) + { + const ScopedLockType sl (getLock()); + + for (int i = getNumSlots(); --i >= 0;) + { + HashEntry* entry = slots.getUnchecked(i); + HashEntry* previous = nullptr; + + while (entry != nullptr) + { + if (entry->value == valueToRemove) + { + const ScopedPointer deleter (entry); + + entry = entry->nextEntry; + + if (previous != nullptr) + previous->nextEntry = entry; + else + slots.set (i, entry); + + --totalNumItems; + } + else + { + previous = entry; + entry = entry->nextEntry; + } + } + } + } + + /** Remaps the hash-map to use a different number of slots for its hash function. + Each slot corresponds to a single hash-code, and each one can contain multiple items. + @see getNumSlots() + */ + void remapTable (int newNumberOfSlots) + { + HashMap newTable (newNumberOfSlots); + + for (int i = getNumSlots(); --i >= 0;) + for (const HashEntry* entry = slots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry) + newTable.set (entry->key, entry->value); + + swapWith (newTable); + } + + /** Returns the number of slots which are available for hashing. + Each slot corresponds to a single hash-code, and each one can contain multiple items. + @see getNumSlots() + */ + inline int getNumSlots() const noexcept + { + return slots.size(); + } + + //============================================================================== + /** Efficiently swaps the contents of two hash-maps. */ + void swapWith (HashMap& otherHashMap) noexcept + { + const ScopedLockType lock1 (getLock()); + const ScopedLockType lock2 (otherHashMap.getLock()); + + slots.swapWithArray (otherHashMap.slots); + std::swap (totalNumItems, otherHashMap.totalNumItems); + } + + //============================================================================== + /** Returns the CriticalSection that locks this structure. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return lock; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + +private: + //============================================================================== + class HashEntry + { + public: + HashEntry (KeyTypeParameter k, ValueTypeParameter val, HashEntry* const next) + : key (k), value (val), nextEntry (next) + {} + + const KeyType key; + ValueType value; + HashEntry* nextEntry; + + BEAST_DECLARE_NON_COPYABLE (HashEntry) + }; + +public: + //============================================================================== + /** Iterates over the items in a HashMap. + + To use it, repeatedly call next() until it returns false, e.g. + @code + HashMap myMap; + + HashMap::Iterator i (myMap); + + while (i.next()) + { + DBG (i.getKey() << " -> " << i.getValue()); + } + @endcode + + The order in which items are iterated bears no resemblence to the order in which + they were originally added! + + Obviously as soon as you call any non-const methods on the original hash-map, any + iterators that were created beforehand will cease to be valid, and should not be used. + + @see HashMap + */ + class Iterator + { + public: + //============================================================================== + Iterator (const HashMap& hashMapToIterate) + : hashMap (hashMapToIterate), entry (nullptr), index (0) + {} + + /** Moves to the next item, if one is available. + When this returns true, you can get the item's key and value using getKey() and + getValue(). If it returns false, the iteration has finished and you should stop. + */ + bool next() + { + if (entry != nullptr) + entry = entry->nextEntry; + + while (entry == nullptr) + { + if (index >= hashMap.getNumSlots()) + return false; + + entry = hashMap.slots.getUnchecked (index++); + } + + return true; + } + + /** Returns the current item's key. + This should only be called when a call to next() has just returned true. + */ + KeyType getKey() const + { + return entry != nullptr ? entry->key : KeyType(); + } + + /** Returns the current item's value. + This should only be called when a call to next() has just returned true. + */ + ValueType getValue() const + { + return entry != nullptr ? entry->value : ValueType(); + } + + private: + //============================================================================== + const HashMap& hashMap; + HashEntry* entry; + int index; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Iterator) + }; + +private: + //============================================================================== + enum { defaultHashTableSize = 101 }; + friend class Iterator; + + Array slots; + int totalNumItems; + TypeOfCriticalSectionToUse lock; + + int generateHashFor (KeyTypeParameter key) const + { + const int hash = HashFunctionToUse::generateHash (key, getNumSlots()); + bassert (isPositiveAndBelow (hash, getNumSlots())); // your hash function is generating out-of-range numbers! + return hash; + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashMap) +}; + + +#endif // BEAST_HASHMAP_BEASTHEADER diff --git a/modules/beast_core/containers/beast_LinkedListPointer.h b/modules/beast_core/containers/beast_LinkedListPointer.h new file mode 100644 index 0000000000..4ac97c5849 --- /dev/null +++ b/modules/beast_core/containers/beast_LinkedListPointer.h @@ -0,0 +1,366 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_LINKEDLISTPOINTER_BEASTHEADER +#define BEAST_LINKEDLISTPOINTER_BEASTHEADER + + +//============================================================================== +/** + Helps to manipulate singly-linked lists of objects. + + For objects that are designed to contain a pointer to the subsequent item in the + list, this class contains methods to deal with the list. To use it, the ObjectType + class that it points to must contain a LinkedListPointer called nextListItem, e.g. + + @code + struct MyObject + { + int x, y, z; + + // A linkable object must contain a member with this name and type, which must be + // accessible by the LinkedListPointer class. (This doesn't mean it has to be public - + // you could make your class a friend of a LinkedListPointer instead). + LinkedListPointer nextListItem; + }; + + LinkedListPointer myList; + myList.append (new MyObject()); + myList.append (new MyObject()); + + int numItems = myList.size(); // returns 2 + MyObject* lastInList = myList.getLast(); + @endcode +*/ +template +class LinkedListPointer +{ +public: + //============================================================================== + /** Creates a null pointer to an empty list. */ + LinkedListPointer() noexcept + : item (nullptr) + { + } + + /** Creates a pointer to a list whose head is the item provided. */ + explicit LinkedListPointer (ObjectType* const headItem) noexcept + : item (headItem) + { + } + + /** Sets this pointer to point to a new list. */ + LinkedListPointer& operator= (ObjectType* const newItem) noexcept + { + item = newItem; + return *this; + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + LinkedListPointer (LinkedListPointer&& other) noexcept + : item (other.item) + { + other.item = nullptr; + } + + LinkedListPointer& operator= (LinkedListPointer&& other) noexcept + { + bassert (this != &other); // hopefully the compiler should make this situation impossible! + + item = other.item; + other.item = nullptr; + return *this; + } + #endif + + //============================================================================== + /** Returns the item which this pointer points to. */ + inline operator ObjectType*() const noexcept + { + return item; + } + + /** Returns the item which this pointer points to. */ + inline ObjectType* get() const noexcept + { + return item; + } + + /** Returns the last item in the list which this pointer points to. + This will iterate the list and return the last item found. Obviously the speed + of this operation will be proportional to the size of the list. If the list is + empty the return value will be this object. + If you're planning on appending a number of items to your list, it's much more + efficient to use the Appender class than to repeatedly call getLast() to find the end. + */ + LinkedListPointer& getLast() noexcept + { + LinkedListPointer* l = this; + + while (l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns the number of items in the list. + Obviously with a simple linked list, getting the size involves iterating the list, so + this can be a lengthy operation - be careful when using this method in your code. + */ + int size() const noexcept + { + int total = 0; + + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + ++total; + + return total; + } + + /** Returns the item at a given index in the list. + Since the only way to find an item is to iterate the list, this operation can obviously + be slow, depending on its size, so you should be careful when using this in algorithms. + */ + LinkedListPointer& operator[] (int index) noexcept + { + LinkedListPointer* l = this; + + while (--index >= 0 && l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns the item at a given index in the list. + Since the only way to find an item is to iterate the list, this operation can obviously + be slow, depending on its size, so you should be careful when using this in algorithms. + */ + const LinkedListPointer& operator[] (int index) const noexcept + { + const LinkedListPointer* l = this; + + while (--index >= 0 && l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns true if the list contains the given item. */ + bool contains (const ObjectType* const itemToLookFor) const noexcept + { + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + if (itemToLookFor == i) + return true; + + return false; + } + + //============================================================================== + /** Inserts an item into the list, placing it before the item that this pointer + currently points to. + */ + void insertNext (ObjectType* const newItem) + { + bassert (newItem != nullptr); + bassert (newItem->nextListItem == nullptr); + newItem->nextListItem = item; + item = newItem; + } + + /** Inserts an item at a numeric index in the list. + Obviously this will involve iterating the list to find the item at the given index, + so be careful about the impact this may have on execution time. + */ + void insertAtIndex (int index, ObjectType* newItem) + { + bassert (newItem != nullptr); + LinkedListPointer* l = this; + + while (index != 0 && l->item != nullptr) + { + l = &(l->item->nextListItem); + --index; + } + + l->insertNext (newItem); + } + + /** Replaces the object that this pointer points to, appending the rest of the list to + the new object, and returning the old one. + */ + ObjectType* replaceNext (ObjectType* const newItem) noexcept + { + bassert (newItem != nullptr); + bassert (newItem->nextListItem == nullptr); + + ObjectType* const oldItem = item; + item = newItem; + item->nextListItem = oldItem->nextListItem.item; + oldItem->nextListItem.item = nullptr; + return oldItem; + } + + /** Adds an item to the end of the list. + + This operation involves iterating the whole list, so can be slow - if you need to + append a number of items to your list, it's much more efficient to use the Appender + class than to repeatedly call append(). + */ + void append (ObjectType* const newItem) + { + getLast().item = newItem; + } + + /** Creates copies of all the items in another list and adds them to this one. + This will use the ObjectType's copy constructor to try to create copies of each + item in the other list, and appends them to this list. + */ + void addCopyOfList (const LinkedListPointer& other) + { + LinkedListPointer* insertPoint = this; + + for (ObjectType* i = other.item; i != nullptr; i = i->nextListItem) + { + insertPoint->insertNext (new ObjectType (*i)); + insertPoint = &(insertPoint->item->nextListItem); + } + } + + /** Removes the head item from the list. + This won't delete the object that is removed, but returns it, so the caller can + delete it if necessary. + */ + ObjectType* removeNext() noexcept + { + ObjectType* const oldItem = item; + + if (oldItem != nullptr) + { + item = oldItem->nextListItem; + oldItem->nextListItem.item = nullptr; + } + + return oldItem; + } + + /** Removes a specific item from the list. + Note that this will not delete the item, it simply unlinks it from the list. + */ + void remove (ObjectType* const itemToRemove) + { + if (LinkedListPointer* const l = findPointerTo (itemToRemove)) + l->removeNext(); + } + + /** Iterates the list, calling the delete operator on all of its elements and + leaving this pointer empty. + */ + void deleteAll() + { + while (item != nullptr) + { + ObjectType* const oldItem = item; + item = oldItem->nextListItem; + delete oldItem; + } + } + + /** Finds a pointer to a given item. + If the item is found in the list, this returns the pointer that points to it. If + the item isn't found, this returns null. + */ + LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept + { + LinkedListPointer* l = this; + + while (l->item != nullptr) + { + if (l->item == itemToLookFor) + return l; + + l = &(l->item->nextListItem); + } + + return nullptr; + } + + /** Copies the items in the list to an array. + The destArray must contain enough elements to hold the entire list - no checks are + made for this! + */ + void copyToArray (ObjectType** destArray) const noexcept + { + bassert (destArray != nullptr); + + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + *destArray++ = i; + } + + /** Swaps this pointer with another one */ + void swapWith (LinkedListPointer& other) noexcept + { + std::swap (item, other.item); + } + + //============================================================================== + /** + Allows efficient repeated insertions into a list. + + You can create an Appender object which points to the last element in your + list, and then repeatedly call Appender::append() to add items to the end + of the list in O(1) time. + */ + class Appender + { + public: + /** Creates an appender which will add items to the given list. + */ + Appender (LinkedListPointer& endOfListPointer) noexcept + : endOfList (&endOfListPointer) + { + // This can only be used to add to the end of a list. + bassert (endOfListPointer.item == nullptr); + } + + /** Appends an item to the list. */ + void append (ObjectType* const newItem) noexcept + { + *endOfList = newItem; + endOfList = &(newItem->nextListItem); + } + + private: + LinkedListPointer* endOfList; + + BEAST_DECLARE_NON_COPYABLE (Appender) + }; + +private: + //============================================================================== + ObjectType* item; + + BEAST_DECLARE_NON_COPYABLE (LinkedListPointer) +}; + + +#endif // BEAST_LINKEDLISTPOINTER_BEASTHEADER diff --git a/modules/beast_core/containers/beast_NamedValueSet.cpp b/modules/beast_core/containers/beast_NamedValueSet.cpp new file mode 100644 index 0000000000..e092865eb4 --- /dev/null +++ b/modules/beast_core/containers/beast_NamedValueSet.cpp @@ -0,0 +1,304 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +NamedValueSet::NamedValue::NamedValue() noexcept +{ +} + +inline NamedValueSet::NamedValue::NamedValue (const Identifier n, const var& v) + : name (n), value (v) +{ +} + +NamedValueSet::NamedValue::NamedValue (const NamedValue& other) + : name (other.name), value (other.value) +{ +} + +NamedValueSet::NamedValue& NamedValueSet::NamedValue::operator= (const NamedValueSet::NamedValue& other) +{ + name = other.name; + value = other.value; + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +NamedValueSet::NamedValue::NamedValue (NamedValue&& other) noexcept + : nextListItem (static_cast &&> (other.nextListItem)), + name (static_cast (other.name)), + value (static_cast (other.value)) +{ +} + +inline NamedValueSet::NamedValue::NamedValue (const Identifier n, var&& v) + : name (n), value (static_cast (v)) +{ +} + +NamedValueSet::NamedValue& NamedValueSet::NamedValue::operator= (NamedValue&& other) noexcept +{ + nextListItem = static_cast &&> (other.nextListItem); + name = static_cast (other.name); + value = static_cast (other.value); + return *this; +} +#endif + +bool NamedValueSet::NamedValue::operator== (const NamedValueSet::NamedValue& other) const noexcept +{ + return name == other.name && value == other.value; +} + +//============================================================================== +NamedValueSet::NamedValueSet() noexcept +{ +} + +NamedValueSet::NamedValueSet (const NamedValueSet& other) +{ + values.addCopyOfList (other.values); +} + +NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) +{ + clear(); + values.addCopyOfList (other.values); + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept + : values (static_cast &&> (other.values)) +{ +} + +NamedValueSet& NamedValueSet::operator= (NamedValueSet&& other) noexcept +{ + other.values.swapWith (values); + return *this; +} +#endif + +NamedValueSet::~NamedValueSet() +{ + clear(); +} + +void NamedValueSet::clear() +{ + values.deleteAll(); +} + +bool NamedValueSet::operator== (const NamedValueSet& other) const +{ + const NamedValue* i1 = values; + const NamedValue* i2 = other.values; + + while (i1 != nullptr && i2 != nullptr) + { + if (! (*i1 == *i2)) + return false; + + i1 = i1->nextListItem; + i2 = i2->nextListItem; + } + + return true; +} + +bool NamedValueSet::operator!= (const NamedValueSet& other) const +{ + return ! operator== (other); +} + +int NamedValueSet::size() const noexcept +{ + return values.size(); +} + +const var& NamedValueSet::operator[] (const Identifier name) const +{ + for (NamedValue* i = values; i != nullptr; i = i->nextListItem) + if (i->name == name) + return i->value; + + return var::null; +} + +var NamedValueSet::getWithDefault (const Identifier name, const var& defaultReturnValue) const +{ + if (const var* const v = getVarPointer (name)) + return *v; + + return defaultReturnValue; +} + +var* NamedValueSet::getVarPointer (const Identifier name) const noexcept +{ + for (NamedValue* i = values; i != nullptr; i = i->nextListItem) + if (i->name == name) + return &(i->value); + + return nullptr; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +bool NamedValueSet::set (const Identifier name, var&& newValue) +{ + LinkedListPointer* i = &values; + + while (i->get() != nullptr) + { + NamedValue* const v = i->get(); + + if (v->name == name) + { + if (v->value.equalsWithSameType (newValue)) + return false; + + v->value = static_cast (newValue); + return true; + } + + i = &(v->nextListItem); + } + + i->insertNext (new NamedValue (name, static_cast (newValue))); + return true; +} +#endif + +bool NamedValueSet::set (const Identifier name, const var& newValue) +{ + LinkedListPointer* i = &values; + + while (i->get() != nullptr) + { + NamedValue* const v = i->get(); + + if (v->name == name) + { + if (v->value.equalsWithSameType (newValue)) + return false; + + v->value = newValue; + return true; + } + + i = &(v->nextListItem); + } + + i->insertNext (new NamedValue (name, newValue)); + return true; +} + +bool NamedValueSet::contains (const Identifier name) const +{ + return getVarPointer (name) != nullptr; +} + +bool NamedValueSet::remove (const Identifier name) +{ + LinkedListPointer* i = &values; + + for (;;) + { + NamedValue* const v = i->get(); + + if (v == nullptr) + break; + + if (v->name == name) + { + delete i->removeNext(); + return true; + } + + i = &(v->nextListItem); + } + + return false; +} + +const Identifier NamedValueSet::getName (const int index) const +{ + const NamedValue* const v = values[index]; + bassert (v != nullptr); + return v->name; +} + +const var& NamedValueSet::getValueAt (const int index) const +{ + const NamedValue* const v = values[index]; + bassert (v != nullptr); + return v->value; +} + +void NamedValueSet::setFromXmlAttributes (const XmlElement& xml) +{ + clear(); + LinkedListPointer::Appender appender (values); + + const int numAtts = xml.getNumAttributes(); // xxx inefficient - should write an att iterator.. + + for (int i = 0; i < numAtts; ++i) + { + const String& name = xml.getAttributeName (i); + const String& value = xml.getAttributeValue (i); + + if (name.startsWith ("base64:")) + { + MemoryBlock mb; + + if (mb.fromBase64Encoding (value)) + { + appender.append (new NamedValue (name.substring (7), var (mb))); + continue; + } + } + + appender.append (new NamedValue (name, var (value))); + } +} + +void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const +{ + for (NamedValue* i = values; i != nullptr; i = i->nextListItem) + { + if (const MemoryBlock* mb = i->value.getBinaryData()) + { + xml.setAttribute ("base64:" + i->name.toString(), + mb->toBase64Encoding()); + } + else + { + // These types can't be stored as XML! + bassert (! i->value.isObject()); + bassert (! i->value.isMethod()); + bassert (! i->value.isArray()); + + xml.setAttribute (i->name.toString(), + i->value.toString()); + } + } +} diff --git a/modules/beast_core/containers/beast_NamedValueSet.h b/modules/beast_core/containers/beast_NamedValueSet.h new file mode 100644 index 0000000000..1c2d08d93b --- /dev/null +++ b/modules/beast_core/containers/beast_NamedValueSet.h @@ -0,0 +1,164 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_NAMEDVALUESET_BEASTHEADER +#define BEAST_NAMEDVALUESET_BEASTHEADER + +#include "beast_Variant.h" +#include "../containers/beast_LinkedListPointer.h" +class XmlElement; +#ifndef DOXYGEN + class JSONFormatter; +#endif + + +//============================================================================== +/** Holds a set of named var objects. + + This can be used as a basic structure to hold a set of var object, which can + be retrieved by using their identifier. +*/ +class BEAST_API NamedValueSet +{ +public: + /** Creates an empty set. */ + NamedValueSet() noexcept; + + /** Creates a copy of another set. */ + NamedValueSet (const NamedValueSet& other); + + /** Replaces this set with a copy of another set. */ + NamedValueSet& operator= (const NamedValueSet& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValueSet (NamedValueSet&& other) noexcept; + NamedValueSet& operator= (NamedValueSet&& other) noexcept; + #endif + + /** Destructor. */ + ~NamedValueSet(); + + bool operator== (const NamedValueSet& other) const; + bool operator!= (const NamedValueSet& other) const; + + //============================================================================== + /** Returns the total number of values that the set contains. */ + int size() const noexcept; + + /** Returns the value of a named item. + If the name isn't found, this will return a void variant. + @see getProperty + */ + const var& operator[] (const Identifier name) const; + + /** Tries to return the named value, but if no such value is found, this will + instead return the supplied default value. + */ + var getWithDefault (const Identifier name, const var& defaultReturnValue) const; + + /** Changes or adds a named value. + @returns true if a value was changed or added; false if the + value was already set the the value passed-in. + */ + bool set (const Identifier name, const var& newValue); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Changes or adds a named value. + @returns true if a value was changed or added; false if the + value was already set the the value passed-in. + */ + bool set (const Identifier name, var&& newValue); + #endif + + /** Returns true if the set contains an item with the specified name. */ + bool contains (const Identifier name) const; + + /** Removes a value from the set. + @returns true if a value was removed; false if there was no value + with the name that was given. + */ + bool remove (const Identifier name); + + /** Returns the name of the value at a given index. + The index must be between 0 and size() - 1. + */ + const Identifier getName (int index) const; + + /** Returns the value of the item at a given index. + The index must be between 0 and size() - 1. + */ + const var& getValueAt (int index) const; + + /** Removes all values. */ + void clear(); + + //============================================================================== + /** Returns a pointer to the var that holds a named value, or null if there is + no value with this name. + + Do not use this method unless you really need access to the internal var object + for some reason - for normal reading and writing always prefer operator[]() and set(). + */ + var* getVarPointer (const Identifier name) const noexcept; + + //============================================================================== + /** Sets properties to the values of all of an XML element's attributes. */ + void setFromXmlAttributes (const XmlElement& xml); + + /** Sets attributes in an XML element corresponding to each of this object's + properties. + */ + void copyToXmlAttributes (XmlElement& xml) const; + +private: + //============================================================================== + class NamedValue + { + public: + NamedValue() noexcept; + NamedValue (const NamedValue&); + NamedValue (const Identifier name, const var& value); + NamedValue& operator= (const NamedValue&); + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValue (NamedValue&&) noexcept; + NamedValue (const Identifier name, var&& value); + NamedValue& operator= (NamedValue&&) noexcept; + #endif + bool operator== (const NamedValue& other) const noexcept; + + LinkedListPointer nextListItem; + Identifier name; + var value; + + private: + BEAST_LEAK_DETECTOR (NamedValue) + }; + + friend class LinkedListPointer; + LinkedListPointer values; + + friend class JSONFormatter; +}; + + +#endif // BEAST_NAMEDVALUESET_BEASTHEADER diff --git a/modules/beast_core/containers/beast_OwnedArray.h b/modules/beast_core/containers/beast_OwnedArray.h new file mode 100644 index 0000000000..4740f8caea --- /dev/null +++ b/modules/beast_core/containers/beast_OwnedArray.h @@ -0,0 +1,865 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_OWNEDARRAY_BEASTHEADER +#define BEAST_OWNEDARRAY_BEASTHEADER + +#include "beast_ArrayAllocationBase.h" +#include "beast_ElementComparator.h" +#include "../threads/beast_CriticalSection.h" + + +//============================================================================== +/** An array designed for holding objects. + + This holds a list of pointers to objects, and will automatically + delete the objects when they are removed from the array, or when the + array is itself deleted. + + Declare it in the form: OwnedArray + + ..and then add new objects, e.g. myOwnedArray.add (new MyObjectClass()); + + After adding objects, they are 'owned' by the array and will be deleted when + removed or replaced. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, ReferenceCountedArray, StringArray, CriticalSection +*/ +template + +class OwnedArray +{ +public: + //============================================================================== + /** Creates an empty array. */ + OwnedArray() noexcept + : numUsed (0) + { + } + + /** Deletes the array and also deletes any objects inside it. + + To get rid of the array without deleting its objects, use its + clear (false) method before deleting it. + */ + ~OwnedArray() + { + deleteAllObjects(); + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + OwnedArray (OwnedArray&& other) noexcept + : data (static_cast &&> (other.data)), + numUsed (other.numUsed) + { + other.numUsed = 0; + } + + OwnedArray& operator= (OwnedArray&& other) noexcept + { + const ScopedLockType lock (getLock()); + deleteAllObjects(); + + data = static_cast &&> (other.data); + numUsed = other.numUsed; + other.numUsed = 0; + return *this; + } + #endif + + //============================================================================== + /** Clears the array, optionally deleting the objects inside it first. */ + void clear (const bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + + if (deleteObjects) + deleteAllObjects(); + + data.setAllocatedSize (0); + numUsed = 0; + } + + //============================================================================== + /** Returns the number of items currently in the array. + @see operator[] + */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns a pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClass* operator[] (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : static_cast (nullptr); + } + + /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. + + This is a faster and less safe version of operator[] which doesn't check the index passed in, so + it can be used when you're sure the index is always going to be legal. + */ + inline ObjectClass* getUnchecked (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + bassert (isPositiveAndBelow (index, numUsed)); + return data.elements [index]; + } + + /** Returns a pointer to the first object in the array. + + This will return a null pointer if the array's empty. + @see getLast + */ + inline ObjectClass* getFirst() const noexcept + { + const ScopedLockType lock (getLock()); + return numUsed > 0 ? data.elements [0] + : static_cast (nullptr); + } + + /** Returns a pointer to the last object in the array. + + This will return a null pointer if the array's empty. + @see getFirst + */ + inline ObjectClass* getLast() const noexcept + { + const ScopedLockType lock (getLock()); + return numUsed > 0 ? data.elements [numUsed - 1] + : static_cast (nullptr); + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ObjectClass** getRawDataPointer() noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** end() const noexcept + { + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of an object which might be in the array. + + @param objectToLookFor the object to look for + @returns the index at which the object was found, or -1 if it's not found + */ + int indexOf (const ObjectClass* const objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass* const* e = data.elements.getData(); + ObjectClass* const* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (objectToLookFor == *e) + return static_cast (e - data.elements.getData()); + + return -1; + } + + /** Returns true if the array contains a specified object. + + @param objectToLookFor the object to look for + @returns true if the object is in the array + */ + bool contains (const ObjectClass* const objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass* const* e = data.elements.getData(); + ObjectClass* const* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (objectToLookFor == *e) + return true; + + return false; + } + + //============================================================================== + /** Appends a new object to the end of the array. + + Note that the this object will be deleted by the OwnedArray when it + is removed, so be careful not to delete it somewhere else. + + Also be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param newObject the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted + */ + void add (const ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + data.elements [numUsed++] = const_cast (newObject); + } + + /** Inserts a new object into the array at the given index. + + Note that the this object will be deleted by the OwnedArray when it + is removed, so be careful not to delete it somewhere else. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + Be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param indexToInsertAt the index at which the new element should be inserted + @param newObject the new object to add to the array + @see add, addSorted, addIfNotAlreadyThere, set + */ + void insert (int indexToInsertAt, + const ObjectClass* const newObject) noexcept + { + if (indexToInsertAt >= 0) + { + const ScopedLockType lock (getLock()); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + + ObjectClass** const e = data.elements + indexToInsertAt; + const int numToMove = numUsed - indexToInsertAt; + + if (numToMove > 0) + memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); + + *e = const_cast (newObject); + ++numUsed; + } + else + { + add (newObject); + } + } + + /** Inserts an array of values into this array at a given position. + + If the index is less than 0 or greater than the size of the array, the + new elements will be added to the end of the array. + Otherwise, they will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the first new element should be inserted + @param newObjects the new values to add to the array + @param numberOfElements how many items are in the array + @see insert, add, addSorted, set + */ + void insertArray (int indexToInsertAt, + ObjectClass* const* newObjects, + int numberOfElements) + { + if (numberOfElements > 0) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + numberOfElements); + ObjectClass** insertPos = data.elements; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos += indexToInsertAt; + const size_t numberToMove = (size_t) (numUsed - indexToInsertAt); + memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ObjectClass*)); + } + else + { + insertPos += numUsed; + } + + numUsed += numberOfElements; + + while (--numberOfElements >= 0) + *insertPos++ = *newObjects++; + } + } + + /** Appends a new object at the end of the array as long as the array doesn't + already contain it. + + If the array already contains a matching object, nothing will be done. + + @param newObject the new object to add to the array + */ + void addIfNotAlreadyThere (const ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + + if (! contains (newObject)) + add (newObject); + } + + /** Replaces an object in the array with a different one. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the new object is added to the end of the array. + + Be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param indexToChange the index whose value you want to change + @param newObject the new value to set for this index. + @param deleteOldElement whether to delete the object that's being replaced with the new one + @see add, insert, remove + */ + void set (const int indexToChange, + const ObjectClass* const newObject, + const bool deleteOldElement = true) + { + if (indexToChange >= 0) + { + ObjectClass* toDelete = nullptr; + + { + const ScopedLockType lock (getLock()); + + if (indexToChange < numUsed) + { + if (deleteOldElement) + { + toDelete = data.elements [indexToChange]; + + if (toDelete == newObject) + toDelete = nullptr; + } + + data.elements [indexToChange] = const_cast (newObject); + } + else + { + data.ensureAllocatedSize (numUsed + 1); + data.elements [numUsed++] = const_cast (newObject); + } + } + + delete toDelete; // don't want to use a ScopedPointer here because if the + // object has a private destructor, both OwnedArray and + // ScopedPointer would need to be friend classes.. + } + else + { + jassertfalse; // you're trying to set an object at a negative index, which doesn't have + // any effect - but since the object is not being added, it may be leaking.. + } + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addArray (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + { + data.elements [numUsed] = arrayToAddFrom.getUnchecked (startIndex++); + ++numUsed; + } + } + + /** Adds copies of the elements in another array to the end of this array. + + The other array must be either an OwnedArray of a compatible type of object, or an Array + containing pointers to the same kind of object. The objects involved must provide + a copy constructor, and this will be used to create new copies of each element, and + add them to this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addCopiesOf (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + { + data.elements [numUsed] = new ObjectClass (*arrayToAddFrom.getUnchecked (startIndex++)); + ++numUsed; + } + } + + /** Inserts a new object into the array assuming that the array is sorted. + + This will use a comparator to find the position at which the new object + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort method + for details about this object's structure + @param newObject the new object to insert to the array + @returns the index at which the new object was added + @see add, sort, indexOfSorted + */ + template + int addSorted (ElementComparator& comparator, ObjectClass* const newObject) noexcept + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + insert (index, newObject); + return index; + } + + /** Finds the index of an object in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param objectToLookFor the object to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, const ObjectClass* const objectToLookFor) const noexcept + { + (void) comparator; + const ScopedLockType lock (getLock()); + int s = 0, e = numUsed; + + while (s < e) + { + if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + break; + + if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + + return -1; + } + + //============================================================================== + /** Removes an object from the array. + + This will remove the object at a given index (optionally also + deleting it) and move back all the subsequent objects to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @param deleteObject whether to delete the object that is removed + @see removeObject, removeRange + */ + void remove (const int indexToRemove, + const bool deleteObject = true) + { + ObjectClass* toDelete = nullptr; + + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (deleteObject) + toDelete = *e; + + --numUsed; + const int numToShift = numUsed - indexToRemove; + + if (numToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); + } + } + + delete toDelete; // don't want to use a ScopedPointer here because if the + // object has a private destructor, both OwnedArray and + // ScopedPointer would need to be friend classes.. + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + /** Removes and returns an object from the array without deleting it. + + This will remove the object at a given index and return it, moving back all + the subsequent objects to close the gap. If the index passed in is out-of-range, + nothing will happen. + + @param indexToRemove the index of the element to remove + @see remove, removeObject, removeRange + */ + ObjectClass* removeAndReturn (const int indexToRemove) + { + ObjectClass* removedItem = nullptr; + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + removedItem = *e; + + --numUsed; + const int numToShift = numUsed - indexToRemove; + + if (numToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + return removedItem; + } + + /** Removes a specified object from the array. + + If the item isn't found, no action is taken. + + @param objectToRemove the object to try to remove + @param deleteObject whether to delete the object (if it's found) + @see remove, removeRange + */ + void removeObject (const ObjectClass* const objectToRemove, + const bool deleteObject = true) + { + const ScopedLockType lock (getLock()); + ObjectClass** const e = data.elements.getData(); + + for (int i = 0; i < numUsed; ++i) + { + if (objectToRemove == e[i]) + { + remove (i, deleteObject); + break; + } + } + } + + /** Removes a range of objects from the array. + + This will remove a set of objects, starting from the given index, + and move any subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first object to remove + @param numberToRemove how many objects should be removed + @param deleteObjects whether to delete the objects that get removed + @see remove, removeObject + */ + void removeRange (int startIndex, + const int numberToRemove, + const bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + const int endIndex = blimit (0, numUsed, startIndex + numberToRemove); + startIndex = blimit (0, numUsed, startIndex); + + if (endIndex > startIndex) + { + if (deleteObjects) + { + for (int i = startIndex; i < endIndex; ++i) + { + delete data.elements [i]; + data.elements [i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) + } + } + + const int rangeSize = endIndex - startIndex; + ObjectClass** e = data.elements + startIndex; + int numToShift = numUsed - endIndex; + numUsed -= rangeSize; + + while (--numToShift >= 0) + { + *e = e [rangeSize]; + ++e; + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes the last n objects from the array. + + @param howManyToRemove how many objects to remove from the end of the array + @param deleteObjects whether to also delete the objects that are removed + @see remove, removeObject, removeRange + */ + void removeLast (int howManyToRemove = 1, + const bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + + if (howManyToRemove >= numUsed) + clear (deleteObjects); + else + removeRange (numUsed - howManyToRemove, howManyToRemove, deleteObjects); + } + + /** Swaps a pair of objects in the array. + + If either of the indexes passed in is out-of-range, nothing will happen, + otherwise the two objects at these positions will be exchanged. + */ + void swap (const int index1, + const int index2) noexcept + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the objects to a different position. + + This will move the object to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the object to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this object to end up. If this + is less than zero, it will be moved to the end of the array + */ + void move (const int currentIndex, + int newIndex) noexcept + { + if (currentIndex != newIndex) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + ObjectClass* const value = data.elements [currentIndex]; + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); + } + + data.elements [newIndex] = value; + } + } + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + void swapWithArray (OwnedArray& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const ScopedLockType lock2 (otherArray.getLock()); + + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + const ScopedLockType lock (getLock()); + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + @see sortArray, indexOfSorted + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) const noexcept + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + const ScopedLockType lock (getLock()); + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void deleteAllObjects() + { + while (numUsed > 0) + delete data.elements [--numUsed]; + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OwnedArray) +}; + + +#endif // BEAST_OWNEDARRAY_BEASTHEADER diff --git a/modules/beast_core/containers/beast_PropertySet.cpp b/modules/beast_core/containers/beast_PropertySet.cpp new file mode 100644 index 0000000000..cf2ff79df2 --- /dev/null +++ b/modules/beast_core/containers/beast_PropertySet.cpp @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +PropertySet::PropertySet (const bool ignoreCaseOfKeyNames) + : properties (ignoreCaseOfKeyNames), + fallbackProperties (nullptr), + ignoreCaseOfKeys (ignoreCaseOfKeyNames) +{ +} + +PropertySet::PropertySet (const PropertySet& other) + : properties (other.properties), + fallbackProperties (other.fallbackProperties), + ignoreCaseOfKeys (other.ignoreCaseOfKeys) +{ +} + +PropertySet& PropertySet::operator= (const PropertySet& other) +{ + properties = other.properties; + fallbackProperties = other.fallbackProperties; + ignoreCaseOfKeys = other.ignoreCaseOfKeys; + + propertyChanged(); + return *this; +} + +PropertySet::~PropertySet() +{ +} + +void PropertySet::clear() +{ + const ScopedLock sl (lock); + + if (properties.size() > 0) + { + properties.clear(); + propertyChanged(); + } +} + +String PropertySet::getValue (const String& keyName, + const String& defaultValue) const noexcept +{ + const ScopedLock sl (lock); + + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index]; + + return fallbackProperties != nullptr ? fallbackProperties->getValue (keyName, defaultValue) + : defaultValue; +} + +int PropertySet::getIntValue (const String& keyName, + const int defaultValue) const noexcept +{ + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index].getIntValue(); + + return fallbackProperties != nullptr ? fallbackProperties->getIntValue (keyName, defaultValue) + : defaultValue; +} + +double PropertySet::getDoubleValue (const String& keyName, + const double defaultValue) const noexcept +{ + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues()[index].getDoubleValue(); + + return fallbackProperties != nullptr ? fallbackProperties->getDoubleValue (keyName, defaultValue) + : defaultValue; +} + +bool PropertySet::getBoolValue (const String& keyName, + const bool defaultValue) const noexcept +{ + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index].getIntValue() != 0; + + return fallbackProperties != nullptr ? fallbackProperties->getBoolValue (keyName, defaultValue) + : defaultValue; +} + +XmlElement* PropertySet::getXmlValue (const String& keyName) const +{ + return XmlDocument::parse (getValue (keyName)); +} + +void PropertySet::setValue (const String& keyName, const var& v) +{ + bassert (keyName.isNotEmpty()); // shouldn't use an empty key name! + + if (keyName.isNotEmpty()) + { + const String value (v.toString()); + const ScopedLock sl (lock); + + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index < 0 || properties.getAllValues() [index] != value) + { + properties.set (keyName, value); + propertyChanged(); + } + } +} + +void PropertySet::removeValue (const String& keyName) +{ + if (keyName.isNotEmpty()) + { + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + { + properties.remove (keyName); + propertyChanged(); + } + } +} + +void PropertySet::setValue (const String& keyName, const XmlElement* const xml) +{ + setValue (keyName, xml == nullptr ? var::null + : var (xml->createDocument (String::empty, true))); +} + +bool PropertySet::containsKey (const String& keyName) const noexcept +{ + const ScopedLock sl (lock); + return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); +} + +void PropertySet::addAllPropertiesFrom (const PropertySet& source) +{ + const ScopedLock sl (source.getLock()); + + for (int i = 0; i < source.properties.size(); ++i) + setValue (source.properties.getAllKeys() [i], + source.properties.getAllValues() [i]); +} + +void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept +{ + const ScopedLock sl (lock); + fallbackProperties = fallbackProperties_; +} + +XmlElement* PropertySet::createXml (const String& nodeName) const +{ + const ScopedLock sl (lock); + XmlElement* const xml = new XmlElement (nodeName); + + for (int i = 0; i < properties.getAllKeys().size(); ++i) + { + XmlElement* const e = xml->createNewChildElement ("VALUE"); + e->setAttribute ("name", properties.getAllKeys()[i]); + e->setAttribute ("val", properties.getAllValues()[i]); + } + + return xml; +} + +void PropertySet::restoreFromXml (const XmlElement& xml) +{ + const ScopedLock sl (lock); + clear(); + + forEachXmlChildElementWithTagName (xml, e, "VALUE") + { + if (e->hasAttribute ("name") + && e->hasAttribute ("val")) + { + properties.set (e->getStringAttribute ("name"), + e->getStringAttribute ("val")); + } + } + + if (properties.size() > 0) + propertyChanged(); +} + +void PropertySet::propertyChanged() +{ +} diff --git a/modules/beast_core/containers/beast_PropertySet.h b/modules/beast_core/containers/beast_PropertySet.h new file mode 100644 index 0000000000..f6513acc35 --- /dev/null +++ b/modules/beast_core/containers/beast_PropertySet.h @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_PROPERTYSET_BEASTHEADER +#define BEAST_PROPERTYSET_BEASTHEADER + +#include "../text/beast_StringPairArray.h" +#include "../xml/beast_XmlElement.h" +#include "../containers/beast_Variant.h" + + +//============================================================================== +/** + A set of named property values, which can be strings, integers, floating point, etc. + + Effectively, this just wraps a StringPairArray in an interface that makes it easier + to load and save types other than strings. + + See the PropertiesFile class for a subclass of this, which automatically broadcasts change + messages and saves/loads the list from a file. +*/ +class BEAST_API PropertySet +{ +public: + //============================================================================== + /** Creates an empty PropertySet. + @param ignoreCaseOfKeyNames if true, the names of properties are compared in a + case-insensitive way + */ + PropertySet (bool ignoreCaseOfKeyNames = false); + + /** Creates a copy of another PropertySet. */ + PropertySet (const PropertySet& other); + + /** Copies another PropertySet over this one. */ + PropertySet& operator= (const PropertySet& other); + + /** Destructor. */ + virtual ~PropertySet(); + + //============================================================================== + /** Returns one of the properties as a string. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + String getValue (const String& keyName, + const String& defaultReturnValue = String::empty) const noexcept; + + /** Returns one of the properties as an integer. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + int getIntValue (const String& keyName, + const int defaultReturnValue = 0) const noexcept; + + /** Returns one of the properties as an double. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + double getDoubleValue (const String& keyName, + const double defaultReturnValue = 0.0) const noexcept; + + /** Returns one of the properties as an boolean. + + The result will be true if the string found for this key name can be parsed as a non-zero + integer. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + bool getBoolValue (const String& keyName, + const bool defaultReturnValue = false) const noexcept; + + /** Returns one of the properties as an XML element. + + The result will a new XMLElement object that the caller must delete. If may return 0 if the + key isn't found, or if the entry contains an string that isn't valid XML. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + */ + XmlElement* getXmlValue (const String& keyName) const; + + //============================================================================== + /** Sets a named property. + + @param keyName the name of the property to set. (This mustn't be an empty string) + @param value the new value to set it to + */ + void setValue (const String& keyName, const var& value); + + /** Sets a named property to an XML element. + + @param keyName the name of the property to set. (This mustn't be an empty string) + @param xml the new element to set it to. If this is zero, the value will be set to + an empty string + @see getXmlValue + */ + void setValue (const String& keyName, const XmlElement* xml); + + /** This copies all the values from a source PropertySet to this one. + This won't remove any existing settings, it just adds any that it finds in the source set. + */ + void addAllPropertiesFrom (const PropertySet& source); + + //============================================================================== + /** Deletes a property. + @param keyName the name of the property to delete. (This mustn't be an empty string) + */ + void removeValue (const String& keyName); + + /** Returns true if the properies include the given key. */ + bool containsKey (const String& keyName) const noexcept; + + /** Removes all values. */ + void clear(); + + //============================================================================== + /** Returns the keys/value pair array containing all the properties. */ + StringPairArray& getAllProperties() noexcept { return properties; } + + /** Returns the lock used when reading or writing to this set */ + const CriticalSection& getLock() const noexcept { return lock; } + + //============================================================================== + /** Returns an XML element which encapsulates all the items in this property set. + The string parameter is the tag name that should be used for the node. + @see restoreFromXml + */ + XmlElement* createXml (const String& nodeName) const; + + /** Reloads a set of properties that were previously stored as XML. + The node passed in must have been created by the createXml() method. + @see createXml + */ + void restoreFromXml (const XmlElement& xml); + + //============================================================================== + /** Sets up a second PopertySet that will be used to look up any values that aren't + set in this one. + + If you set this up to be a pointer to a second property set, then whenever one + of the getValue() methods fails to find an entry in this set, it will look up that + value in the fallback set, and if it finds it, it will return that. + + Make sure that you don't delete the fallback set while it's still being used by + another set! To remove the fallback set, just call this method with a null pointer. + + @see getFallbackPropertySet + */ + void setFallbackPropertySet (PropertySet* fallbackProperties) noexcept; + + /** Returns the fallback property set. + @see setFallbackPropertySet + */ + PropertySet* getFallbackPropertySet() const noexcept { return fallbackProperties; } + +protected: + /** Subclasses can override this to be told when one of the properies has been changed. */ + virtual void propertyChanged(); + +private: + StringPairArray properties; + PropertySet* fallbackProperties; + CriticalSection lock; + bool ignoreCaseOfKeys; + + BEAST_LEAK_DETECTOR (PropertySet) +}; + + +#endif // BEAST_PROPERTYSET_BEASTHEADER diff --git a/modules/beast_core/containers/beast_ReferenceCountedArray.h b/modules/beast_core/containers/beast_ReferenceCountedArray.h new file mode 100644 index 0000000000..dd4add79ba --- /dev/null +++ b/modules/beast_core/containers/beast_ReferenceCountedArray.h @@ -0,0 +1,855 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_REFERENCECOUNTEDARRAY_BEASTHEADER +#define BEAST_REFERENCECOUNTEDARRAY_BEASTHEADER + +#include "../memory/beast_ReferenceCountedObject.h" +#include "beast_ArrayAllocationBase.h" +#include "beast_ElementComparator.h" +#include "../threads/beast_CriticalSection.h" + + +//============================================================================== +/** + Holds a list of objects derived from ReferenceCountedObject. + + A ReferenceCountedArray holds objects derived from ReferenceCountedObject, + and takes care of incrementing and decrementing their ref counts when they + are added and removed from the array. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, OwnedArray, StringArray +*/ +template +class ReferenceCountedArray +{ +public: + typedef ReferenceCountedObjectPtr ObjectClassPtr; + + //============================================================================== + /** Creates an empty array. + @see ReferenceCountedObject, Array, OwnedArray + */ + ReferenceCountedArray() noexcept + : numUsed (0) + { + } + + /** Creates a copy of another array */ + ReferenceCountedArray (const ReferenceCountedArray& other) noexcept + { + const ScopedLockType lock (other.getLock()); + numUsed = other.size(); + data.setAllocatedSize (numUsed); + memcpy (data.elements, other.getRawDataPointer(), numUsed * sizeof (ObjectClass*)); + + for (int i = numUsed; --i >= 0;) + if (ObjectClass* o = data.elements[i]) + o->incReferenceCount(); + } + + /** Creates a copy of another array */ + template + ReferenceCountedArray (const ReferenceCountedArray& other) noexcept + { + const typename ReferenceCountedArray::ScopedLockType lock (other.getLock()); + numUsed = other.size(); + data.setAllocatedSize (numUsed); + memcpy (data.elements, other.getRawDataPointer(), numUsed * sizeof (ObjectClass*)); + + for (int i = numUsed; --i >= 0;) + if (ObjectClass* o = data.elements[i]) + o->incReferenceCount(); + } + + /** Copies another array into this one. + Any existing objects in this array will first be released. + */ + ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept + { + ReferenceCountedArray otherCopy (other); + swapWithArray (otherCopy); + return *this; + } + + /** Copies another array into this one. + Any existing objects in this array will first be released. + */ + template + ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept + { + ReferenceCountedArray otherCopy (other); + swapWithArray (otherCopy); + return *this; + } + + /** Destructor. + Any objects in the array will be released, and may be deleted if not referenced from elsewhere. + */ + ~ReferenceCountedArray() + { + clear(); + } + + //============================================================================== + /** Removes all objects from the array. + + Any objects in the array that are not referenced from elsewhere will be deleted. + */ + void clear() + { + const ScopedLockType lock (getLock()); + + while (numUsed > 0) + if (ObjectClass* o = data.elements [--numUsed]) + o->decReferenceCount(); + + bassert (numUsed == 0); + data.setAllocatedSize (0); + } + + /** Returns the current number of objects in the array. */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns a pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClassPtr operator[] (const int index) const noexcept + { + return getObjectPointer (index); + } + + /** Returns a pointer to the object at this index in the array, without checking + whether the index is in-range. + + This is a faster and less safe version of operator[] which doesn't check the index passed in, so + it can be used when you're sure the index is always going to be legal. + */ + inline ObjectClassPtr getUnchecked (const int index) const noexcept + { + return getObjectPointerUnchecked (index); + } + + /** Returns a raw pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClass* getObjectPointer (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : nullptr; + } + + /** Returns a raw pointer to the object at this index in the array, without checking + whether the index is in-range. + */ + inline ObjectClass* getObjectPointerUnchecked (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + bassert (isPositiveAndBelow (index, numUsed)); + return data.elements [index]; + } + + /** Returns a pointer to the first object in the array. + + This will return a null pointer if the array's empty. + @see getLast + */ + inline ObjectClassPtr getFirst() const noexcept + { + const ScopedLockType lock (getLock()); + return numUsed > 0 ? data.elements [0] + : static_cast (nullptr); + } + + /** Returns a pointer to the last object in the array. + + This will return a null pointer if the array's empty. + @see getFirst + */ + inline ObjectClassPtr getLast() const noexcept + { + const ScopedLockType lock (getLock()); + return numUsed > 0 ? data.elements [numUsed - 1] + : static_cast (nullptr); + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ObjectClass** getRawDataPointer() const noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** end() const noexcept + { + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of the first occurrence of an object in the array. + + @param objectToLookFor the object to look for + @returns the index at which the object was found, or -1 if it's not found + */ + int indexOf (const ObjectClass* const objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass** e = data.elements.getData(); + ObjectClass** const endPointer = e + numUsed; + + while (e != endPointer) + { + if (objectToLookFor == *e) + return static_cast (e - data.elements.getData()); + + ++e; + } + + return -1; + } + + /** Returns true if the array contains a specified object. + + @param objectToLookFor the object to look for + @returns true if the object is in the array + */ + bool contains (const ObjectClass* const objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass** e = data.elements.getData(); + ObjectClass** const endPointer = e + numUsed; + + while (e != endPointer) + { + if (objectToLookFor == *e) + return true; + + ++e; + } + + return false; + } + + /** Appends a new object to the end of the array. + + This will increase the new object's reference count. + + @param newObject the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addArray + */ + void add (ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + data.elements [numUsed++] = newObject; + + if (newObject != nullptr) + newObject->incReferenceCount(); + } + + /** Inserts a new object into the array at the given index. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + This will increase the new object's reference count. + + @param indexToInsertAt the index at which the new element should be inserted + @param newObject the new object to add to the array + @see add, addSorted, addIfNotAlreadyThere, set + */ + void insert (int indexToInsertAt, + ObjectClass* const newObject) noexcept + { + if (indexToInsertAt >= 0) + { + const ScopedLockType lock (getLock()); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + + ObjectClass** const e = data.elements + indexToInsertAt; + const int numToMove = numUsed - indexToInsertAt; + + if (numToMove > 0) + memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); + + *e = newObject; + + if (newObject != nullptr) + newObject->incReferenceCount(); + + ++numUsed; + } + else + { + add (newObject); + } + } + + /** Appends a new object at the end of the array as long as the array doesn't + already contain it. + + If the array already contains a matching object, nothing will be done. + + @param newObject the new object to add to the array + */ + void addIfNotAlreadyThere (ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + if (! contains (newObject)) + add (newObject); + } + + /** Replaces an object in the array with a different one. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the new object is added to the end of the array. + + The object being added has its reference count increased, and if it's replacing + another object, then that one has its reference count decreased, and may be deleted. + + @param indexToChange the index whose value you want to change + @param newObject the new value to set for this index. + @see add, insert, remove + */ + void set (const int indexToChange, + ObjectClass* const newObject) + { + if (indexToChange >= 0) + { + const ScopedLockType lock (getLock()); + + if (newObject != nullptr) + newObject->incReferenceCount(); + + if (indexToChange < numUsed) + { + if (ObjectClass* o = data.elements [indexToChange]) + o->decReferenceCount(); + + data.elements [indexToChange] = newObject; + } + else + { + data.ensureAllocatedSize (numUsed + 1); + data.elements [numUsed++] = newObject; + } + } + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + void addArray (const ReferenceCountedArray& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) noexcept + { + const ScopedLockType lock1 (arrayToAddFrom.getLock()); + + { + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + if (numElementsToAdd > 0) + { + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + add (arrayToAddFrom.getUnchecked (startIndex++)); + } + } + } + + /** Inserts a new object into the array assuming that the array is sorted. + + This will use a comparator to find the position at which the new object + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator object to use to compare the elements - see the + sort() method for details about this object's form + @param newObject the new object to insert to the array + @returns the index at which the new object was added + @see add, sort + */ + template + int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept + { + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + insert (index, newObject); + return index; + } + + /** Inserts or replaces an object in the array, assuming it is sorted. + + This is similar to addSorted, but if a matching element already exists, then it will be + replaced by the new one, rather than the new one being added as well. + */ + template + void addOrReplaceSorted (ElementComparator& comparator, + ObjectClass* newObject) noexcept + { + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + + if (index > 0 && comparator.compareElements (newObject, data.elements [index - 1]) == 0) + set (index - 1, newObject); // replace an existing object that matches + else + insert (index, newObject); // no match, so insert the new one + } + + /** Finds the index of an object in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param objectToLookFor the object to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, + const ObjectClass* const objectToLookFor) const noexcept + { + (void) comparator; + const ScopedLockType lock (getLock()); + int s = 0, e = numUsed; + + while (s < e) + { + if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + break; + + if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + + return -1; + } + + //============================================================================== + /** Removes an object from the array. + + This will remove the object at a given index and move back all the + subsequent objects to close the gap. + + If the index passed in is out-of-range, nothing will happen. + + The object that is removed will have its reference count decreased, + and may be deleted if not referenced from elsewhere. + + @param indexToRemove the index of the element to remove + @see removeObject, removeRange + */ + void remove (const int indexToRemove) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (ObjectClass* o = *e) + o->decReferenceCount(); + + --numUsed; + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes and returns an object from the array. + + This will remove the object at a given index and return it, moving back all + the subsequent objects to close the gap. If the index passed in is out-of-range, + nothing will happen and a null pointer will be returned. + + @param indexToRemove the index of the element to remove + @see remove, removeObject, removeRange + */ + ObjectClassPtr removeAndReturn (const int indexToRemove) + { + ObjectClassPtr removedItem; + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (ObjectClass* o = *e) + { + removedItem = o; + o->decReferenceCount(); + } + + --numUsed; + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + return removedItem; + } + + /** Removes the first occurrence of a specified object from the array. + + If the item isn't found, no action is taken. If it is found, it is + removed and has its reference count decreased. + + @param objectToRemove the object to try to remove + @see remove, removeRange + */ + void removeObject (ObjectClass* const objectToRemove) + { + const ScopedLockType lock (getLock()); + remove (indexOf (objectToRemove)); + } + + /** Removes a range of objects from the array. + + This will remove a set of objects, starting from the given index, + and move any subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + The objects that are removed will have their reference counts decreased, + and may be deleted if not referenced from elsewhere. + + @param startIndex the index of the first object to remove + @param numberToRemove how many objects should be removed + @see remove, removeObject + */ + void removeRange (const int startIndex, + const int numberToRemove) + { + const ScopedLockType lock (getLock()); + + const int start = blimit (0, numUsed, startIndex); + const int endIndex = blimit (0, numUsed, startIndex + numberToRemove); + + if (endIndex > start) + { + int i; + for (i = start; i < endIndex; ++i) + { + if (ObjectClass* o = data.elements[i]) + { + o->decReferenceCount(); + data.elements[i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) + } + } + + const int rangeSize = endIndex - start; + ObjectClass** e = data.elements + start; + i = numUsed - endIndex; + numUsed -= rangeSize; + + while (--i >= 0) + { + *e = e [rangeSize]; + ++e; + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes the last n objects from the array. + + The objects that are removed will have their reference counts decreased, + and may be deleted if not referenced from elsewhere. + + @param howManyToRemove how many objects to remove from the end of the array + @see remove, removeObject, removeRange + */ + void removeLast (int howManyToRemove = 1) + { + const ScopedLockType lock (getLock()); + + if (howManyToRemove > numUsed) + howManyToRemove = numUsed; + + while (--howManyToRemove >= 0) + remove (numUsed - 1); + } + + /** Swaps a pair of objects in the array. + + If either of the indexes passed in is out-of-range, nothing will happen, + otherwise the two objects at these positions will be exchanged. + */ + void swap (const int index1, + const int index2) noexcept + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the objects to a different position. + + This will move the object to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the object to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this object to end up. If this + is less than zero, it will be moved to the end of the array + */ + void move (const int currentIndex, + int newIndex) noexcept + { + if (currentIndex != newIndex) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + ObjectClass* const value = data.elements [currentIndex]; + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); + } + + data.elements [newIndex] = value; + } + } + } + + //============================================================================== + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + void swapWithArray (ReferenceCountedArray& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const ScopedLockType lock2 (otherArray.getLock()); + + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + //============================================================================== + /** Compares this array to another one. + + @returns true only if the other array contains the same objects in the same order + */ + bool operator== (const ReferenceCountedArray& other) const noexcept + { + const ScopedLockType lock2 (other.getLock()); + const ScopedLockType lock1 (getLock()); + + if (numUsed != other.numUsed) + return false; + + for (int i = numUsed; --i >= 0;) + if (data.elements [i] != other.data.elements [i]) + return false; + + return true; + } + + /** Compares this array to another one. + + @see operator== + */ + bool operator!= (const ReferenceCountedArray& other) const noexcept + { + return ! operator== (other); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + + @see sortArray + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) const noexcept + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + const ScopedLockType lock (getLock()); + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + const ScopedLockType lock (getLock()); + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; +}; + + +#endif // BEAST_REFERENCECOUNTEDARRAY_BEASTHEADER diff --git a/modules/beast_core/containers/beast_ScopedValueSetter.h b/modules/beast_core/containers/beast_ScopedValueSetter.h new file mode 100644 index 0000000000..945ee13a5d --- /dev/null +++ b/modules/beast_core/containers/beast_ScopedValueSetter.h @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SCOPEDVALUESETTER_BEASTHEADER +#define BEAST_SCOPEDVALUESETTER_BEASTHEADER + + +//============================================================================== +/** + Helper class providing an RAII-based mechanism for temporarily setting and + then re-setting a value. + + E.g. @code + int x = 1; + + { + ScopedValueSetter setter (x, 2); + + // x is now 2 + } + + // x is now 1 again + + { + ScopedValueSetter setter (x, 3, 4); + + // x is now 3 + } + + // x is now 4 + @endcode + +*/ +template +class ScopedValueSetter +{ +public: + /** Creates a ScopedValueSetter that will immediately change the specified value to the + given new value, and will then reset it to its original value when this object is deleted. + */ + ScopedValueSetter (ValueType& valueToSet, + ValueType newValue) + : value (valueToSet), + originalValue (valueToSet) + { + valueToSet = newValue; + } + + /** Creates a ScopedValueSetter that will immediately change the specified value to the + given new value, and will then reset it to be valueWhenDeleted when this object is deleted. + */ + ScopedValueSetter (ValueType& valueToSet, + ValueType newValue, + ValueType valueWhenDeleted) + : value (valueToSet), + originalValue (valueWhenDeleted) + { + valueToSet = newValue; + } + + ~ScopedValueSetter() + { + value = originalValue; + } + +private: + //============================================================================== + ValueType& value; + const ValueType originalValue; + + BEAST_DECLARE_NON_COPYABLE (ScopedValueSetter) +}; + + +#endif // BEAST_SCOPEDVALUESETTER_BEASTHEADER diff --git a/modules/beast_core/containers/beast_SortedSet.h b/modules/beast_core/containers/beast_SortedSet.h new file mode 100644 index 0000000000..ee6e106fc6 --- /dev/null +++ b/modules/beast_core/containers/beast_SortedSet.h @@ -0,0 +1,494 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SORTEDSET_BEASTHEADER +#define BEAST_SORTEDSET_BEASTHEADER + +#include "beast_ArrayAllocationBase.h" +#include "../threads/beast_CriticalSection.h" + +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4512) +#endif + + +//============================================================================== +/** + Holds a set of unique primitive objects, such as ints or doubles. + + A set can only hold one item with a given value, so if for example it's a + set of integers, attempting to add the same integer twice will do nothing + the second time. + + Internally, the list of items is kept sorted (which means that whatever + kind of primitive type is used must support the ==, <, >, <= and >= operators + to determine the order), and searching the set for known values is very fast + because it uses a binary-chop method. + + Note that if you're using a class or struct as the element type, it must be + capable of being copied or moved with a straightforward memcpy, rather than + needing construction and destruction code. + + To make all the set's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, OwnedArray, ReferenceCountedArray, StringArray, CriticalSection +*/ +template +class SortedSet +{ +public: + //============================================================================== + /** Creates an empty set. */ + SortedSet() noexcept + { + } + + /** Creates a copy of another set. + @param other the set to copy + */ + SortedSet (const SortedSet& other) + : data (other.data) + { + } + + /** Destructor. */ + ~SortedSet() noexcept + { + } + + /** Copies another set over this one. + @param other the set to copy + */ + SortedSet& operator= (const SortedSet& other) noexcept + { + data = other.data; + return *this; + } + + //============================================================================== + /** Compares this set to another one. + Two sets are considered equal if they both contain the same set of elements. + @param other the other set to compare with + */ + bool operator== (const SortedSet& other) const noexcept + { + return data == other.data; + } + + /** Compares this set to another one. + Two sets are considered equal if they both contain the same set of elements. + @param other the other set to compare with + */ + bool operator!= (const SortedSet& other) const noexcept + { + return ! operator== (other); + } + + //============================================================================== + /** Removes all elements from the set. + + This will remove all the elements, and free any storage that the set is + using. To clear it without freeing the storage, use the clearQuick() + method instead. + + @see clearQuick + */ + void clear() noexcept + { + data.clear(); + } + + /** Removes all elements from the set without freeing the array's allocated storage. + + @see clear + */ + void clearQuick() noexcept + { + data.clearQuick(); + } + + //============================================================================== + /** Returns the current number of elements in the set. + */ + inline int size() const noexcept + { + return data.size(); + } + + /** Returns one of the elements in the set. + + If the index passed in is beyond the range of valid elements, this + will return zero. + + If you're certain that the index will always be a valid element, you + can call getUnchecked() instead, which is faster. + + @param index the index of the element being requested (0 is the first element in the set) + @see getUnchecked, getFirst, getLast + */ + inline ElementType operator[] (const int index) const noexcept + { + return data [index]; + } + + /** Returns one of the elements in the set, without checking the index passed in. + Unlike the operator[] method, this will try to return an element without + checking that the index is within the bounds of the set, so should only + be used when you're confident that it will always be a valid index. + + @param index the index of the element being requested (0 is the first element in the set) + @see operator[], getFirst, getLast + */ + inline ElementType getUnchecked (const int index) const noexcept + { + return data.getUnchecked (index); + } + + /** Returns a direct reference to one of the elements in the set, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + */ + inline ElementType& getReference (const int index) const noexcept + { + return data.getReference (index); + } + + /** Returns the first element in the set, or 0 if the set is empty. + + @see operator[], getUnchecked, getLast + */ + inline ElementType getFirst() const noexcept + { + return data.getFirst(); + } + + /** Returns the last element in the set, or 0 if the set is empty. + + @see operator[], getUnchecked, getFirst + */ + inline ElementType getLast() const noexcept + { + return data.getLast(); + } + + //============================================================================== + /** Returns a pointer to the first element in the set. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* begin() const noexcept + { + return data.begin(); + } + + /** Returns a pointer to the element which follows the last element in the set. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* end() const noexcept + { + return data.end(); + } + + //============================================================================== + /** Finds the index of the first element which matches the value passed in. + + This will search the set for the given object, and return the index + of its first occurrence. If the object isn't found, the method will return -1. + + @param elementToLookFor the value or object to look for + @returns the index of the object, or -1 if it's not found + */ + int indexOf (const ElementType& elementToLookFor) const noexcept + { + const ScopedLockType lock (data.getLock()); + + int s = 0; + int e = data.size(); + + for (;;) + { + if (s >= e) + return -1; + + if (elementToLookFor == data.getReference (s)) + return s; + + const int halfway = (s + e) / 2; + + if (halfway == s) + return -1; + else if (elementToLookFor < data.getReference (halfway)) + e = halfway; + else + s = halfway; + } + } + + /** Returns true if the set contains at least one occurrence of an object. + + @param elementToLookFor the value or object to look for + @returns true if the item is found + */ + bool contains (const ElementType& elementToLookFor) const noexcept + { + return indexOf (elementToLookFor) >= 0; + } + + //============================================================================== + /** Adds a new element to the set, (as long as it's not already in there). + + Note that if a matching element already exists, the new value will be assigned + to the existing one using operator=, so that if there are any differences between + the objects which were not recognised by the object's operator==, then the + set will always contain a copy of the most recently added one. + + @param newElement the new object to add to the set + @returns true if the value was added, or false if it already existed + @see set, insert, addIfNotAlreadyThere, addSorted, addSet, addArray + */ + bool add (const ElementType& newElement) noexcept + { + const ScopedLockType lock (getLock()); + + int s = 0; + int e = data.size(); + + while (s < e) + { + ElementType& elem = data.getReference (s); + if (newElement == elem) + { + elem = newElement; // force an update in case operator== permits differences. + return false; + } + + const int halfway = (s + e) / 2; + const bool isBeforeHalfway = (newElement < data.getReference (halfway)); + + if (halfway == s) + { + if (! isBeforeHalfway) + ++s; + + break; + } + else if (isBeforeHalfway) + e = halfway; + else + s = halfway; + } + + data.insert (s, newElement); + return true; + } + + /** Adds elements from an array to this set. + + @param elementsToAdd the array of elements to add + @param numElementsToAdd how many elements are in this other array + @see add + */ + void addArray (const ElementType* elementsToAdd, + int numElementsToAdd) noexcept + { + const ScopedLockType lock (getLock()); + + while (--numElementsToAdd >= 0) + add (*elementsToAdd++); + } + + /** Adds elements from another set to this one. + + @param setToAddFrom the set from which to copy the elements + @param startIndex the first element of the other set to start copying from + @param numElementsToAdd how many elements to add from the other set. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addSet (const OtherSetType& setToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) noexcept + { + const typename OtherSetType::ScopedLockType lock1 (setToAddFrom.getLock()); + + { + const ScopedLockType lock2 (getLock()); + bassert (this != &setToAddFrom); + + if (this != &setToAddFrom) + { + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) + numElementsToAdd = setToAddFrom.size() - startIndex; + + if (numElementsToAdd > 0) + addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); + } + } + } + + //============================================================================== + /** Removes an element from the set. + + This will remove the element at a given index. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @returns the element that has been removed + @see removeValue, removeRange + */ + ElementType remove (const int indexToRemove) noexcept + { + return data.remove (indexToRemove); + } + + /** Removes an item from the set. + + This will remove the given element from the set, if it's there. + + @param valueToRemove the object to try to remove + @see remove, removeRange + */ + void removeValue (const ElementType valueToRemove) noexcept + { + const ScopedLockType lock (getLock()); + data.remove (indexOf (valueToRemove)); + } + + /** Removes any elements which are also in another set. + + @param otherSet the other set in which to look for elements to remove + @see removeValuesNotIn, remove, removeValue, removeRange + */ + template + void removeValuesIn (const OtherSetType& otherSet) noexcept + { + const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this == &otherSet) + { + clear(); + } + else if (otherSet.size() > 0) + { + for (int i = data.size(); --i >= 0;) + if (otherSet.contains (data.getReference (i))) + remove (i); + } + } + + /** Removes any elements which are not found in another set. + + Only elements which occur in this other set will be retained. + + @param otherSet the set in which to look for elements NOT to remove + @see removeValuesIn, remove, removeValue, removeRange + */ + template + void removeValuesNotIn (const OtherSetType& otherSet) noexcept + { + const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this != &otherSet) + { + if (otherSet.size() <= 0) + { + clear(); + } + else + { + for (int i = data.size(); --i >= 0;) + if (! otherSet.contains (data.getReference (i))) + remove (i); + } + } + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + void swapWith (SortedSet& otherSet) noexcept + { + data.swapWithArray (otherSet.data); + } + + //============================================================================== + /** Reduces the amount of storage being used by the set. + + Sets typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + data.minimiseStorageOverheads(); + } + + /** Increases the set's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the set won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + data.ensureStorageAllocated (minNumElements); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data.getLock(); } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + +private: + //============================================================================== + Array data; +}; + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +#endif // BEAST_SORTEDSET_BEASTHEADER diff --git a/modules/beast_core/containers/beast_SparseSet.h b/modules/beast_core/containers/beast_SparseSet.h new file mode 100644 index 0000000000..129c26d6eb --- /dev/null +++ b/modules/beast_core/containers/beast_SparseSet.h @@ -0,0 +1,296 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SPARSESET_BEASTHEADER +#define BEAST_SPARSESET_BEASTHEADER + +#include "../maths/beast_Range.h" +#include "../threads/beast_CriticalSection.h" + + +//============================================================================== +/** + Holds a set of primitive values, storing them as a set of ranges. + + This container acts like an array, but can efficiently hold large contiguous + ranges of values. It's quite a specialised class, mostly useful for things + like keeping the set of selected rows in a listbox. + + The type used as a template paramter must be an integer type, such as int, short, + int64, etc. +*/ +template +class SparseSet +{ +public: + //============================================================================== + /** Creates a new empty set. */ + SparseSet() + { + } + + /** Creates a copy of another SparseSet. */ + SparseSet (const SparseSet& other) + : values (other.values) + { + } + + //============================================================================== + /** Clears the set. */ + void clear() + { + values.clear(); + } + + /** Checks whether the set is empty. + + This is much quicker than using (size() == 0). + */ + bool isEmpty() const noexcept + { + return values.size() == 0; + } + + /** Returns the number of values in the set. + + Because of the way the data is stored, this method can take longer if there + are a lot of items in the set. Use isEmpty() for a quick test of whether there + are any items. + */ + Type size() const + { + Type total (0); + + for (int i = 0; i < values.size(); i += 2) + total += values.getUnchecked (i + 1) - values.getUnchecked (i); + + return total; + } + + /** Returns one of the values in the set. + + @param index the index of the value to retrieve, in the range 0 to (size() - 1). + @returns the value at this index, or 0 if it's out-of-range + */ + Type operator[] (Type index) const + { + for (int i = 0; i < values.size(); i += 2) + { + const Type start (values.getUnchecked (i)); + const Type len (values.getUnchecked (i + 1) - start); + + if (index < len) + return start + index; + + index -= len; + } + + return Type(); + } + + /** Checks whether a particular value is in the set. */ + bool contains (const Type valueToLookFor) const + { + for (int i = 0; i < values.size(); ++i) + if (valueToLookFor < values.getUnchecked(i)) + return (i & 1) != 0; + + return false; + } + + //============================================================================== + /** Returns the number of contiguous blocks of values. + @see getRange + */ + int getNumRanges() const noexcept + { + return values.size() >> 1; + } + + /** Returns one of the contiguous ranges of values stored. + @param rangeIndex the index of the range to look up, between 0 + and (getNumRanges() - 1) + @see getTotalRange + */ + const Range getRange (const int rangeIndex) const + { + if (isPositiveAndBelow (rangeIndex, getNumRanges())) + return Range (values.getUnchecked (rangeIndex << 1), + values.getUnchecked ((rangeIndex << 1) + 1)); + + return Range(); + } + + /** Returns the range between the lowest and highest values in the set. + @see getRange + */ + Range getTotalRange() const + { + if (values.size() > 0) + { + bassert ((values.size() & 1) == 0); + return Range (values.getUnchecked (0), + values.getUnchecked (values.size() - 1)); + } + + return Range(); + } + + //============================================================================== + /** Adds a range of contiguous values to the set. + e.g. addRange (Range \ (10, 14)) will add (10, 11, 12, 13) to the set. + */ + void addRange (const Range range) + { + bassert (range.getLength() >= 0); + if (range.getLength() > 0) + { + removeRange (range); + + values.addUsingDefaultSort (range.getStart()); + values.addUsingDefaultSort (range.getEnd()); + + simplify(); + } + } + + /** Removes a range of values from the set. + e.g. removeRange (Range\ (10, 14)) will remove (10, 11, 12, 13) from the set. + */ + void removeRange (const Range rangeToRemove) + { + bassert (rangeToRemove.getLength() >= 0); + + if (rangeToRemove.getLength() > 0 + && values.size() > 0 + && rangeToRemove.getStart() < values.getUnchecked (values.size() - 1) + && values.getUnchecked(0) < rangeToRemove.getEnd()) + { + const bool onAtStart = contains (rangeToRemove.getStart() - 1); + const Type lastValue (bmin (rangeToRemove.getEnd(), values.getLast())); + const bool onAtEnd = contains (lastValue); + + for (int i = values.size(); --i >= 0;) + { + if (values.getUnchecked(i) <= lastValue) + { + while (values.getUnchecked(i) >= rangeToRemove.getStart()) + { + values.remove (i); + + if (--i < 0) + break; + } + + break; + } + } + + if (onAtStart) values.addUsingDefaultSort (rangeToRemove.getStart()); + if (onAtEnd) values.addUsingDefaultSort (lastValue); + + simplify(); + } + } + + /** Does an XOR of the values in a given range. */ + void invertRange (const Range range) + { + SparseSet newItems; + newItems.addRange (range); + + for (int i = getNumRanges(); --i >= 0;) + newItems.removeRange (getRange (i)); + + removeRange (range); + + for (int i = newItems.getNumRanges(); --i >= 0;) + addRange (newItems.getRange(i)); + } + + /** Checks whether any part of a given range overlaps any part of this set. */ + bool overlapsRange (const Range range) + { + if (range.getLength() > 0) + { + for (int i = getNumRanges(); --i >= 0;) + { + if (values.getUnchecked ((i << 1) + 1) <= range.getStart()) + return false; + + if (values.getUnchecked (i << 1) < range.getEnd()) + return true; + } + } + + return false; + } + + /** Checks whether the whole of a given range is contained within this one. */ + bool containsRange (const Range range) + { + if (range.getLength() > 0) + { + for (int i = getNumRanges(); --i >= 0;) + { + if (values.getUnchecked ((i << 1) + 1) <= range.getStart()) + return false; + + if (values.getUnchecked (i << 1) <= range.getStart() + && range.getEnd() <= values.getUnchecked ((i << 1) + 1)) + return true; + } + } + + return false; + } + + //============================================================================== + bool operator== (const SparseSet& other) noexcept + { + return values == other.values; + } + + bool operator!= (const SparseSet& other) noexcept + { + return values != other.values; + } + +private: + //============================================================================== + // alternating start/end values of ranges of values that are present. + Array values; + + void simplify() + { + bassert ((values.size() & 1) == 0); + + for (int i = values.size(); --i > 0;) + if (values.getUnchecked(i) == values.getUnchecked (i - 1)) + values.removeRange (--i, 2); + } +}; + + + +#endif // BEAST_SPARSESET_BEASTHEADER diff --git a/modules/beast_core/containers/beast_Variant.cpp b/modules/beast_core/containers/beast_Variant.cpp new file mode 100644 index 0000000000..8dd827bec1 --- /dev/null +++ b/modules/beast_core/containers/beast_Variant.cpp @@ -0,0 +1,704 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +enum VariantStreamMarkers +{ + varMarker_Int = 1, + varMarker_BoolTrue = 2, + varMarker_BoolFalse = 3, + varMarker_Double = 4, + varMarker_String = 5, + varMarker_Int64 = 6, + varMarker_Array = 7, + varMarker_Binary = 8 +}; + +//============================================================================== +class var::VariantType +{ +public: + VariantType() noexcept {} + virtual ~VariantType() noexcept {} + + virtual int toInt (const ValueUnion&) const noexcept { return 0; } + virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; } + virtual double toDouble (const ValueUnion&) const noexcept { return 0; } + virtual String toString (const ValueUnion&) const { return String::empty; } + virtual bool toBool (const ValueUnion&) const noexcept { return false; } + virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } + virtual Array* toArray (const ValueUnion&) const noexcept { return nullptr; } + virtual MemoryBlock* toBinary (const ValueUnion&) const noexcept { return nullptr; } + + virtual bool isVoid() const noexcept { return false; } + virtual bool isInt() const noexcept { return false; } + virtual bool isInt64() const noexcept { return false; } + virtual bool isBool() const noexcept { return false; } + virtual bool isDouble() const noexcept { return false; } + virtual bool isString() const noexcept { return false; } + virtual bool isObject() const noexcept { return false; } + virtual bool isArray() const noexcept { return false; } + virtual bool isBinary() const noexcept { return false; } + virtual bool isMethod() const noexcept { return false; } + + virtual void cleanUp (ValueUnion&) const noexcept {} + virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } + virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept = 0; + virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; +}; + +//============================================================================== +class var::VariantType_Void : public var::VariantType +{ +public: + VariantType_Void() noexcept {} + static const VariantType_Void instance; + + bool isVoid() const noexcept { return true; } + bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept { return otherType.isVoid(); } + void writeToStream (const ValueUnion&, OutputStream& output) const { output.writeCompressedInt (0); } +}; + +//============================================================================== +class var::VariantType_Int : public var::VariantType +{ +public: + VariantType_Int() noexcept {} + static const VariantType_Int instance; + + int toInt (const ValueUnion& data) const noexcept { return data.intValue; }; + int64 toInt64 (const ValueUnion& data) const noexcept { return (int64) data.intValue; }; + double toDouble (const ValueUnion& data) const noexcept { return (double) data.intValue; } + String toString (const ValueUnion& data) const { return String (data.intValue); } + bool toBool (const ValueUnion& data) const noexcept { return data.intValue != 0; } + bool isInt() const noexcept { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return otherType.toInt (otherData) == data.intValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + output.writeCompressedInt (5); + output.writeByte (varMarker_Int); + output.writeInt (data.intValue); + } +}; + +//============================================================================== +class var::VariantType_Int64 : public var::VariantType +{ +public: + VariantType_Int64() noexcept {} + static const VariantType_Int64 instance; + + int toInt (const ValueUnion& data) const noexcept { return (int) data.int64Value; }; + int64 toInt64 (const ValueUnion& data) const noexcept { return data.int64Value; }; + double toDouble (const ValueUnion& data) const noexcept { return (double) data.int64Value; } + String toString (const ValueUnion& data) const { return String (data.int64Value); } + bool toBool (const ValueUnion& data) const noexcept { return data.int64Value != 0; } + bool isInt64() const noexcept { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return otherType.toInt64 (otherData) == data.int64Value; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + output.writeCompressedInt (9); + output.writeByte (varMarker_Int64); + output.writeInt64 (data.int64Value); + } +}; + +//============================================================================== +class var::VariantType_Double : public var::VariantType +{ +public: + VariantType_Double() noexcept {} + static const VariantType_Double instance; + + int toInt (const ValueUnion& data) const noexcept { return (int) data.doubleValue; }; + int64 toInt64 (const ValueUnion& data) const noexcept { return (int64) data.doubleValue; }; + double toDouble (const ValueUnion& data) const noexcept { return data.doubleValue; } + String toString (const ValueUnion& data) const { return String (data.doubleValue); } + bool toBool (const ValueUnion& data) const noexcept { return data.doubleValue != 0; } + bool isDouble() const noexcept { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return std::abs (otherType.toDouble (otherData) - data.doubleValue) < std::numeric_limits::epsilon(); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + output.writeCompressedInt (9); + output.writeByte (varMarker_Double); + output.writeDouble (data.doubleValue); + } +}; + +//============================================================================== +class var::VariantType_Bool : public var::VariantType +{ +public: + VariantType_Bool() noexcept {} + static const VariantType_Bool instance; + + int toInt (const ValueUnion& data) const noexcept { return data.boolValue ? 1 : 0; }; + int64 toInt64 (const ValueUnion& data) const noexcept { return data.boolValue ? 1 : 0; }; + double toDouble (const ValueUnion& data) const noexcept { return data.boolValue ? 1.0 : 0.0; } + String toString (const ValueUnion& data) const { return String::charToString (data.boolValue ? (beast_wchar) '1' : (beast_wchar) '0'); } + bool toBool (const ValueUnion& data) const noexcept { return data.boolValue; } + bool isBool() const noexcept { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return otherType.toBool (otherData) == data.boolValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + output.writeCompressedInt (1); + output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse); + } +}; + +//============================================================================== +class var::VariantType_String : public var::VariantType +{ +public: + VariantType_String() noexcept {} + static const VariantType_String instance; + + void cleanUp (ValueUnion& data) const noexcept { getString (data)-> ~String(); } + void createCopy (ValueUnion& dest, const ValueUnion& source) const { new (dest.stringValue) String (*getString (source)); } + + bool isString() const noexcept { return true; } + int toInt (const ValueUnion& data) const noexcept { return getString (data)->getIntValue(); }; + int64 toInt64 (const ValueUnion& data) const noexcept { return getString (data)->getLargeIntValue(); }; + double toDouble (const ValueUnion& data) const noexcept { return getString (data)->getDoubleValue(); } + String toString (const ValueUnion& data) const { return *getString (data); } + bool toBool (const ValueUnion& data) const noexcept { return getString (data)->getIntValue() != 0 + || getString (data)->trim().equalsIgnoreCase ("true") + || getString (data)->trim().equalsIgnoreCase ("yes"); } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return otherType.toString (otherData) == *getString (data); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + const String* const s = getString (data); + const size_t len = s->getNumBytesAsUTF8() + 1; + HeapBlock temp (len); + s->copyToUTF8 (temp, len); + output.writeCompressedInt ((int) (len + 1)); + output.writeByte (varMarker_String); + output.write (temp, len); + } + +private: + static inline const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } + static inline String* getString (ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } +}; + +//============================================================================== +class var::VariantType_Object : public var::VariantType +{ +public: + VariantType_Object() noexcept {} + static const VariantType_Object instance; + + void cleanUp (ValueUnion& data) const noexcept { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } + + void createCopy (ValueUnion& dest, const ValueUnion& source) const + { + dest.objectValue = source.objectValue; + if (dest.objectValue != nullptr) + dest.objectValue->incReferenceCount(); + } + + String toString (const ValueUnion& data) const { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } + bool toBool (const ValueUnion& data) const noexcept { return data.objectValue != 0; } + ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept { return data.objectValue; } + bool isObject() const noexcept { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return otherType.toObject (otherData) == data.objectValue; + } + + void writeToStream (const ValueUnion&, OutputStream& output) const + { + jassertfalse; // Can't write an object to a stream! + output.writeCompressedInt (0); + } +}; + +//============================================================================== +class var::VariantType_Array : public var::VariantType +{ +public: + VariantType_Array() noexcept {} + static const VariantType_Array instance; + + void cleanUp (ValueUnion& data) const noexcept { delete data.arrayValue; } + void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.arrayValue = new Array (*(source.arrayValue)); } + + String toString (const ValueUnion&) const { return "[Array]"; } + bool isArray() const noexcept { return true; } + Array* toArray (const ValueUnion& data) const noexcept { return data.arrayValue; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + const Array* const otherArray = otherType.toArray (otherData); + return otherArray != nullptr && *otherArray == *(data.arrayValue); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + MemoryOutputStream buffer (512); + const int numItems = data.arrayValue->size(); + buffer.writeCompressedInt (numItems); + + for (int i = 0; i < numItems; ++i) + data.arrayValue->getReference(i).writeToStream (buffer); + + output.writeCompressedInt (1 + (int) buffer.getDataSize()); + output.writeByte (varMarker_Array); + output << buffer; + } +}; + +//============================================================================== +class var::VariantType_Binary : public var::VariantType +{ +public: + VariantType_Binary() noexcept {} + + static const VariantType_Binary instance; + + void cleanUp (ValueUnion& data) const noexcept { delete data.binaryValue; } + void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest.binaryValue = new MemoryBlock (*source.binaryValue); } + + String toString (const ValueUnion& data) const { return data.binaryValue->toBase64Encoding(); } + bool isBinary() const noexcept { return true; } + MemoryBlock* toBinary (const ValueUnion& data) const noexcept { return data.binaryValue; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + const MemoryBlock* const otherBlock = otherType.toBinary (otherData); + return otherBlock != nullptr && *otherBlock == *data.binaryValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const + { + output.writeCompressedInt (1 + (int) data.binaryValue->getSize()); + output.writeByte (varMarker_Binary); + output << *data.binaryValue; + } +}; + +//============================================================================== +class var::VariantType_Method : public var::VariantType +{ +public: + VariantType_Method() noexcept {} + static const VariantType_Method instance; + + String toString (const ValueUnion&) const { return "Method"; } + bool toBool (const ValueUnion& data) const noexcept { return data.methodValue != nullptr; } + bool isMethod() const noexcept { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept + { + return otherType.isMethod() && otherData.methodValue == data.methodValue; + } + + void writeToStream (const ValueUnion&, OutputStream& output) const + { + jassertfalse; // Can't write a method to a stream! + output.writeCompressedInt (0); + } +}; + +//============================================================================== +const var::VariantType_Void var::VariantType_Void::instance; +const var::VariantType_Int var::VariantType_Int::instance; +const var::VariantType_Int64 var::VariantType_Int64::instance; +const var::VariantType_Bool var::VariantType_Bool::instance; +const var::VariantType_Double var::VariantType_Double::instance; +const var::VariantType_String var::VariantType_String::instance; +const var::VariantType_Object var::VariantType_Object::instance; +const var::VariantType_Array var::VariantType_Array::instance; +const var::VariantType_Binary var::VariantType_Binary::instance; +const var::VariantType_Method var::VariantType_Method::instance; + + +//============================================================================== +var::var() noexcept : type (&VariantType_Void::instance) +{ +} + +var::~var() noexcept +{ + type->cleanUp (value); +} + +const var var::null; + +//============================================================================== +var::var (const var& valueToCopy) : type (valueToCopy.type) +{ + type->createCopy (value, valueToCopy.value); +} + +var::var (const int v) noexcept : type (&VariantType_Int::instance) { value.intValue = v; } +var::var (const int64 v) noexcept : type (&VariantType_Int64::instance) { value.int64Value = v; } +var::var (const bool v) noexcept : type (&VariantType_Bool::instance) { value.boolValue = v; } +var::var (const double v) noexcept : type (&VariantType_Double::instance) { value.doubleValue = v; } +var::var (MethodFunction m) noexcept : type (&VariantType_Method::instance) { value.methodValue = m; } +var::var (const Array& v) : type (&VariantType_Array::instance) { value.arrayValue = new Array (v); } +var::var (const String& v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const char* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const wchar_t* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const void* v, size_t sz) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v, sz); } +var::var (const MemoryBlock& v) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v); } + +var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) +{ + value.objectValue = object; + + if (object != nullptr) + object->incReferenceCount(); +} + + +//============================================================================== +bool var::isVoid() const noexcept { return type->isVoid(); } +bool var::isInt() const noexcept { return type->isInt(); } +bool var::isInt64() const noexcept { return type->isInt64(); } +bool var::isBool() const noexcept { return type->isBool(); } +bool var::isDouble() const noexcept { return type->isDouble(); } +bool var::isString() const noexcept { return type->isString(); } +bool var::isObject() const noexcept { return type->isObject(); } +bool var::isArray() const noexcept { return type->isArray(); } +bool var::isBinaryData() const noexcept { return type->isBinary(); } +bool var::isMethod() const noexcept { return type->isMethod(); } + +var::operator int() const noexcept { return type->toInt (value); } +var::operator int64() const noexcept { return type->toInt64 (value); } +var::operator bool() const noexcept { return type->toBool (value); } +var::operator float() const noexcept { return (float) type->toDouble (value); } +var::operator double() const noexcept { return type->toDouble (value); } +String var::toString() const { return type->toString (value); } +var::operator String() const { return type->toString (value); } +ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); } +Array* var::getArray() const noexcept { return type->toArray (value); } +MemoryBlock* var::getBinaryData() const noexcept { return type->toBinary (value); } +DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast (getObject()); } + +//============================================================================== +void var::swapWith (var& other) noexcept +{ + std::swap (type, other.type); + std::swap (value, other.value); +} + +var& var::operator= (const var& v) { type->cleanUp (value); type = v.type; type->createCopy (value, v.value); return *this; } +var& var::operator= (const int v) { type->cleanUp (value); type = &VariantType_Int::instance; value.intValue = v; return *this; } +var& var::operator= (const int64 v) { type->cleanUp (value); type = &VariantType_Int64::instance; value.int64Value = v; return *this; } +var& var::operator= (const bool v) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = v; return *this; } +var& var::operator= (const double v) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = v; return *this; } +var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const Array& v) { var v2 (v); swapWith (v2); return *this; } +var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } +var& var::operator= (MethodFunction v) { var v2 (v); swapWith (v2); return *this; } + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +var::var (var&& other) noexcept + : type (other.type), + value (other.value) +{ + other.type = &VariantType_Void::instance; +} + +var& var::operator= (var&& other) noexcept +{ + swapWith (other); + return *this; +} + +var::var (String&& v) : type (&VariantType_String::instance) +{ + new (value.stringValue) String (static_cast (v)); +} + +var::var (MemoryBlock&& v) : type (&VariantType_Binary::instance) +{ + value.binaryValue = new MemoryBlock (static_cast (v)); +} + +var& var::operator= (String&& v) +{ + type->cleanUp (value); + type = &VariantType_String::instance; + new (value.stringValue) String (static_cast (v)); + return *this; +} +#endif + +//============================================================================== +bool var::equals (const var& other) const noexcept +{ + return type->equals (value, other.value, *other.type); +} + +bool var::equalsWithSameType (const var& other) const noexcept +{ + return type == other.type && equals (other); +} + +bool operator== (const var& v1, const var& v2) noexcept { return v1.equals (v2); } +bool operator!= (const var& v1, const var& v2) noexcept { return ! v1.equals (v2); } +bool operator== (const var& v1, const String& v2) { return v1.toString() == v2; } +bool operator!= (const var& v1, const String& v2) { return v1.toString() != v2; } +bool operator== (const var& v1, const char* const v2) { return v1.toString() == v2; } +bool operator!= (const var& v1, const char* const v2) { return v1.toString() != v2; } + + +//============================================================================== +var var::operator[] (const Identifier propertyName) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->getProperty (propertyName); + + return var::null; +} + +var var::operator[] (const char* const propertyName) const +{ + return operator[] (Identifier (propertyName)); +} + +var var::getProperty (const Identifier propertyName, const var& defaultReturnValue) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->getProperties().getWithDefault (propertyName, defaultReturnValue); + + return defaultReturnValue; +} + +var var::invoke (const Identifier method, const var* arguments, int numArguments) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->invokeMethod (method, arguments, numArguments); + + return var::null; +} + +var var::invokeMethod (DynamicObject* const target, const var* const arguments, const int numArguments) const +{ + bassert (target != nullptr); + + if (isMethod()) + return (target->*(value.methodValue)) (arguments, numArguments); + + return var::null; +} + +var var::call (const Identifier method) const +{ + return invoke (method, nullptr, 0); +} + +var var::call (const Identifier method, const var& arg1) const +{ + return invoke (method, &arg1, 1); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2) const +{ + var args[] = { arg1, arg2 }; + return invoke (method, args, 2); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2, const var& arg3) +{ + var args[] = { arg1, arg2, arg3 }; + return invoke (method, args, 3); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const +{ + var args[] = { arg1, arg2, arg3, arg4 }; + return invoke (method, args, 4); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const +{ + var args[] = { arg1, arg2, arg3, arg4, arg5 }; + return invoke (method, args, 5); +} + +//============================================================================== +int var::size() const +{ + if (const Array* const array = getArray()) + return array->size(); + + return 0; +} + +const var& var::operator[] (int arrayIndex) const +{ + const Array* const array = getArray(); + + // When using this method, the var must actually be an array, and the index + // must be in-range! + bassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); + + return array->getReference (arrayIndex); +} + +var& var::operator[] (int arrayIndex) +{ + const Array* const array = getArray(); + + // When using this method, the var must actually be an array, and the index + // must be in-range! + bassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); + + return array->getReference (arrayIndex); +} + +Array* var::convertToArray() +{ + Array* array = getArray(); + + if (array == nullptr) + { + const Array tempVar; + var v (tempVar); + array = v.value.arrayValue; + + if (! isVoid()) + array->add (*this); + + swapWith (v); + } + + return array; +} + +void var::append (const var& n) +{ + convertToArray()->add (n); +} + +void var::remove (const int index) +{ + if (Array* const array = getArray()) + array->remove (index); +} + +void var::insert (const int index, const var& n) +{ + convertToArray()->insert (index, n); +} + +void var::resize (const int numArrayElementsWanted) +{ + convertToArray()->resize (numArrayElementsWanted); +} + +int var::indexOf (const var& n) const +{ + if (const Array* const array = getArray()) + return array->indexOf (n); + + return -1; +} + +//============================================================================== +void var::writeToStream (OutputStream& output) const +{ + type->writeToStream (value, output); +} + +var var::readFromStream (InputStream& input) +{ + const int numBytes = input.readCompressedInt(); + + if (numBytes > 0) + { + switch (input.readByte()) + { + case varMarker_Int: return var (input.readInt()); + case varMarker_Int64: return var (input.readInt64()); + case varMarker_BoolTrue: return var (true); + case varMarker_BoolFalse: return var (false); + case varMarker_Double: return var (input.readDouble()); + case varMarker_String: + { + MemoryOutputStream mo; + mo.writeFromInputStream (input, numBytes - 1); + return var (mo.toUTF8()); + } + + case varMarker_Binary: + { + MemoryBlock mb (numBytes - 1); + + if (numBytes > 1) + { + const int numRead = input.read (mb.getData(), numBytes - 1); + mb.setSize (numRead); + } + + return var (mb); + } + + case varMarker_Array: + { + var v; + Array* const destArray = v.convertToArray(); + + for (int i = input.readCompressedInt(); --i >= 0;) + destArray->add (readFromStream (input)); + + return v; + } + + default: + input.skipNextBytes (numBytes - 1); break; + } + } + + return var::null; +} diff --git a/modules/beast_core/containers/beast_Variant.h b/modules/beast_core/containers/beast_Variant.h new file mode 100644 index 0000000000..460d526da5 --- /dev/null +++ b/modules/beast_core/containers/beast_Variant.h @@ -0,0 +1,302 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_VARIANT_BEASTHEADER +#define BEAST_VARIANT_BEASTHEADER + +#include "../text/beast_Identifier.h" +#include "../streams/beast_OutputStream.h" +#include "../streams/beast_InputStream.h" +#include "../containers/beast_Array.h" + +#ifndef DOXYGEN + class ReferenceCountedObject; + class DynamicObject; +#endif + +//============================================================================== +/** + A variant class, that can be used to hold a range of primitive values. + + A var object can hold a range of simple primitive values, strings, or + any kind of ReferenceCountedObject. The var class is intended to act like + the kind of values used in dynamic scripting languages. + + You can save/load var objects either in a small, proprietary binary format + using writeToStream()/readFromStream(), or as JSON by using the JSON class. + + @see JSON, DynamicObject +*/ +class BEAST_API var +{ +public: + //============================================================================== + typedef const var (DynamicObject::*MethodFunction) (const var* arguments, int numArguments); + typedef Identifier identifier; + + //============================================================================== + /** Creates a void variant. */ + var() noexcept; + + /** Destructor. */ + ~var() noexcept; + + /** A static var object that can be used where you need an empty variant object. */ + static const var null; + + var (const var& valueToCopy); + var (int value) noexcept; + var (int64 value) noexcept; + var (bool value) noexcept; + var (double value) noexcept; + var (const char* value); + var (const wchar_t* value); + var (const String& value); + var (const Array& value); + var (ReferenceCountedObject* object); + var (MethodFunction method) noexcept; + var (const void* binaryData, size_t dataSize); + var (const MemoryBlock& binaryData); + + var& operator= (const var& valueToCopy); + var& operator= (int value); + var& operator= (int64 value); + var& operator= (bool value); + var& operator= (double value); + var& operator= (const char* value); + var& operator= (const wchar_t* value); + var& operator= (const String& value); + var& operator= (const Array& value); + var& operator= (ReferenceCountedObject* object); + var& operator= (MethodFunction method); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + var (var&& other) noexcept; + var (String&& value); + var (MemoryBlock&& binaryData); + var& operator= (var&& other) noexcept; + var& operator= (String&& value); + #endif + + void swapWith (var& other) noexcept; + + //============================================================================== + operator int() const noexcept; + operator int64() const noexcept; + operator bool() const noexcept; + operator float() const noexcept; + operator double() const noexcept; + operator String() const; + String toString() const; + + /** If this variant holds an array, this provides access to it. + NOTE: Beware when you use this - the array pointer is only valid for the lifetime + of the variant that returned it, so be very careful not to call this method on temporary + var objects that are the return-value of a function, and which may go out of scope before + you use the array! + */ + Array* getArray() const noexcept; + + /** If this variant holds a memory block, this provides access to it. + NOTE: Beware when you use this - the MemoryBlock pointer is only valid for the lifetime + of the variant that returned it, so be very careful not to call this method on temporary + var objects that are the return-value of a function, and which may go out of scope before + you use the MemoryBlock! + */ + MemoryBlock* getBinaryData() const noexcept; + + ReferenceCountedObject* getObject() const noexcept; + DynamicObject* getDynamicObject() const noexcept; + + //============================================================================== + bool isVoid() const noexcept; + bool isInt() const noexcept; + bool isInt64() const noexcept; + bool isBool() const noexcept; + bool isDouble() const noexcept; + bool isString() const noexcept; + bool isObject() const noexcept; + bool isArray() const noexcept; + bool isBinaryData() const noexcept; + bool isMethod() const noexcept; + + /** Returns true if this var has the same value as the one supplied. + Note that this ignores the type, so a string var "123" and an integer var with the + value 123 are considered to be equal. + @see equalsWithSameType + */ + bool equals (const var& other) const noexcept; + + /** Returns true if this var has the same value and type as the one supplied. + This differs from equals() because e.g. "123" and 123 will be considered different. + @see equals + */ + bool equalsWithSameType (const var& other) const noexcept; + + //============================================================================== + /** If the var is an array, this returns the number of elements. + If the var isn't actually an array, this will return 0. + */ + int size() const; + + /** If the var is an array, this can be used to return one of its elements. + To call this method, you must make sure that the var is actually an array, and + that the index is a valid number. If these conditions aren't met, behaviour is + undefined. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + const var& operator[] (int arrayIndex) const; + + /** If the var is an array, this can be used to return one of its elements. + To call this method, you must make sure that the var is actually an array, and + that the index is a valid number. If these conditions aren't met, behaviour is + undefined. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + var& operator[] (int arrayIndex); + + /** Appends an element to the var, converting it to an array if it isn't already one. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array. The parameter value + will then be appended to it. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void append (const var& valueToAppend); + + /** Inserts an element to the var, converting it to an array if it isn't already one. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array. The parameter value + will then be inserted into it. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void insert (int index, const var& value); + + /** If the var is an array, this removes one of its elements. + If the index is out-of-range or the var isn't an array, nothing will be done. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void remove (int index); + + /** Treating the var as an array, this resizes it to contain the specified number of elements. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array before resizing. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void resize (int numArrayElementsWanted); + + /** If the var is an array, this searches it for the first occurrence of the specified value, + and returns its index. + If the var isn't an array, or if the value isn't found, this returns -1. + */ + int indexOf (const var& value) const; + + //============================================================================== + /** If this variant is an object, this returns one of its properties. */ + var operator[] (const Identifier propertyName) const; + /** If this variant is an object, this returns one of its properties. */ + var operator[] (const char* propertyName) const; + /** If this variant is an object, this returns one of its properties, or a default + fallback value if the property is not set. */ + var getProperty (const Identifier propertyName, const var& defaultReturnValue) const; + + /** If this variant is an object, this invokes one of its methods with no arguments. */ + var call (const Identifier method) const; + /** If this variant is an object, this invokes one of its methods with one argument. */ + var call (const Identifier method, const var& arg1) const; + /** If this variant is an object, this invokes one of its methods with 2 arguments. */ + var call (const Identifier method, const var& arg1, const var& arg2) const; + /** If this variant is an object, this invokes one of its methods with 3 arguments. */ + var call (const Identifier method, const var& arg1, const var& arg2, const var& arg3); + /** If this variant is an object, this invokes one of its methods with 4 arguments. */ + var call (const Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const; + /** If this variant is an object, this invokes one of its methods with 5 arguments. */ + var call (const Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const; + /** If this variant is an object, this invokes one of its methods with a list of arguments. */ + var invoke (const Identifier method, const var* arguments, int numArguments) const; + + //============================================================================== + /** Writes a binary representation of this value to a stream. + The data can be read back later using readFromStream(). + @see JSON + */ + void writeToStream (OutputStream& output) const; + + /** Reads back a stored binary representation of a value. + The data in the stream must have been written using writeToStream(), or this + will have unpredictable results. + @see JSON + */ + static var readFromStream (InputStream& input); + +private: + //============================================================================== + class VariantType; friend class VariantType; + class VariantType_Void; friend class VariantType_Void; + class VariantType_Int; friend class VariantType_Int; + class VariantType_Int64; friend class VariantType_Int64; + class VariantType_Double; friend class VariantType_Double; + class VariantType_Bool; friend class VariantType_Bool; + class VariantType_String; friend class VariantType_String; + class VariantType_Object; friend class VariantType_Object; + class VariantType_Array; friend class VariantType_Array; + class VariantType_Binary; friend class VariantType_Binary; + class VariantType_Method; friend class VariantType_Method; + + union ValueUnion + { + int intValue; + int64 int64Value; + bool boolValue; + double doubleValue; + char stringValue [sizeof (String)]; + ReferenceCountedObject* objectValue; + Array* arrayValue; + MemoryBlock* binaryValue; + MethodFunction methodValue; + }; + + const VariantType* type; + ValueUnion value; + + Array* convertToArray(); + friend class DynamicObject; + var invokeMethod (DynamicObject*, const var*, int) const; +}; + +/** Compares the values of two var objects, using the var::equals() comparison. */ +bool operator== (const var& v1, const var& v2) noexcept; +/** Compares the values of two var objects, using the var::equals() comparison. */ +bool operator!= (const var& v1, const var& v2) noexcept; +bool operator== (const var& v1, const String& v2); +bool operator!= (const var& v1, const String& v2); +bool operator== (const var& v1, const char* v2); +bool operator!= (const var& v1, const char* v2); + + +#endif // BEAST_VARIANT_BEASTHEADER diff --git a/modules/beast_core/files/beast_DirectoryIterator.cpp b/modules/beast_core/files/beast_DirectoryIterator.cpp new file mode 100644 index 0000000000..7d18294125 --- /dev/null +++ b/modules/beast_core/files/beast_DirectoryIterator.cpp @@ -0,0 +1,154 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +static StringArray parseWildcards (const String& pattern) +{ + StringArray s; + s.addTokens (pattern, ";,", "\"'"); + s.trim(); + s.removeEmptyStrings(); + return s; +} + +static bool fileMatches (const StringArray& wildCards, const String& filename) +{ + for (int i = 0; i < wildCards.size(); ++i) + if (filename.matchesWildcard (wildCards[i], ! File::areFileNamesCaseSensitive())) + return true; + + return false; +} + +DirectoryIterator::DirectoryIterator (const File& directory, bool recursive, + const String& pattern, const int type) + : wildCards (parseWildcards (pattern)), + fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern), + wildCard (pattern), + path (File::addTrailingSeparator (directory.getFullPathName())), + index (-1), + totalNumFiles (-1), + whatToLookFor (type), + isRecursive (recursive), + hasBeenAdvanced (false) +{ + // you have to specify the type of files you're looking for! + bassert ((type & (File::findFiles | File::findDirectories)) != 0); + bassert (type > 0 && type <= 7); +} + +DirectoryIterator::~DirectoryIterator() +{ +} + +bool DirectoryIterator::next() +{ + return next (nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); +} + +bool DirectoryIterator::next (bool* const isDirResult, bool* const isHiddenResult, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + hasBeenAdvanced = true; + + if (subIterator != nullptr) + { + if (subIterator->next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly)) + return true; + + subIterator = nullptr; + } + + String filename; + bool isDirectory, isHidden = false; + + while (fileFinder.next (filename, &isDirectory, + (isHiddenResult != nullptr || (whatToLookFor & File::ignoreHiddenFiles) != 0) ? &isHidden : nullptr, + fileSize, modTime, creationTime, isReadOnly)) + { + ++index; + + if (! filename.containsOnly (".")) + { + bool matches = false; + + if (isDirectory) + { + if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) + subIterator = new DirectoryIterator (File::createFileWithoutCheckingPath (path + filename), + true, wildCard, whatToLookFor); + + matches = (whatToLookFor & File::findDirectories) != 0; + } + else + { + matches = (whatToLookFor & File::findFiles) != 0; + } + + // if recursive, we're not relying on the OS iterator to do the wildcard match, so do it now.. + if (matches && isRecursive) + matches = fileMatches (wildCards, filename); + + if (matches && (whatToLookFor & File::ignoreHiddenFiles) != 0) + matches = ! isHidden; + + if (matches) + { + currentFile = File::createFileWithoutCheckingPath (path + filename); + if (isHiddenResult != nullptr) *isHiddenResult = isHidden; + if (isDirResult != nullptr) *isDirResult = isDirectory; + + return true; + } + + if (subIterator != nullptr) + return next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly); + } + } + + return false; +} + +const File& DirectoryIterator::getFile() const +{ + if (subIterator != nullptr && subIterator->hasBeenAdvanced) + return subIterator->getFile(); + + // You need to call DirectoryIterator::next() before asking it for the file that it found! + bassert (hasBeenAdvanced); + + return currentFile; +} + +float DirectoryIterator::getEstimatedProgress() const +{ + if (totalNumFiles < 0) + totalNumFiles = File (path).getNumberOfChildFiles (File::findFilesAndDirectories); + + if (totalNumFiles <= 0) + return 0.0f; + + const float detailedIndex = (subIterator != nullptr) ? index + subIterator->getEstimatedProgress() + : (float) index; + + return detailedIndex / totalNumFiles; +} diff --git a/modules/beast_core/files/beast_DirectoryIterator.h b/modules/beast_core/files/beast_DirectoryIterator.h new file mode 100644 index 0000000000..3ba068a899 --- /dev/null +++ b/modules/beast_core/files/beast_DirectoryIterator.h @@ -0,0 +1,154 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_DIRECTORYITERATOR_BEASTHEADER +#define BEAST_DIRECTORYITERATOR_BEASTHEADER + +#include "beast_File.h" +#include "../memory/beast_ScopedPointer.h" + + +//============================================================================== +/** + Searches through a the files in a directory, returning each file that is found. + + A DirectoryIterator will search through a directory and its subdirectories using + a wildcard filepattern match. + + If you may be finding a large number of files, this is better than + using File::findChildFiles() because it doesn't block while it finds them + all, and this is more memory-efficient. + + It can also guess how far it's got using a wildly inaccurate algorithm. +*/ +class BEAST_API DirectoryIterator +{ +public: + //============================================================================== + /** Creates a DirectoryIterator for a given directory. + + After creating one of these, call its next() method to get the + first file - e.g. @code + + DirectoryIterator iter (File ("/animals/mooses"), true, "*.moose"); + + while (iter.next()) + { + File theFileItFound (iter.getFile()); + + ... etc + } + @endcode + + @param directory the directory to search in + @param isRecursive whether all the subdirectories should also be searched + @param wildCard the file pattern to match. This may contain multiple patterns + separated by a semi-colon or comma, e.g. "*.jpg;*.png" + @param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying + whether to look for files, directories, or both. + */ + DirectoryIterator (const File& directory, + bool isRecursive, + const String& wildCard = "*", + int whatToLookFor = File::findFiles); + + /** Destructor. */ + ~DirectoryIterator(); + + /** Moves the iterator along to the next file. + + @returns true if a file was found (you can then use getFile() to see what it was) - or + false if there are no more matching files. + */ + bool next(); + + /** Moves the iterator along to the next file, and returns various properties of that file. + + If you need to find out details about the file, it's more efficient to call this method than + to call the normal next() method and then find out the details afterwards. + + All the parameters are optional, so pass null pointers for any items that you're not + interested in. + + @returns true if a file was found (you can then use getFile() to see what it was) - or + false if there are no more matching files. If it returns false, then none of the + parameters will be filled-in. + */ + bool next (bool* isDirectory, + bool* isHidden, + int64* fileSize, + Time* modTime, + Time* creationTime, + bool* isReadOnly); + + /** Returns the file that the iterator is currently pointing at. + + The result of this call is only valid after a call to next() has returned true. + */ + const File& getFile() const; + + /** Returns a guess of how far through the search the iterator has got. + + @returns a value 0.0 to 1.0 to show the progress, although this won't be + very accurate. + */ + float getEstimatedProgress() const; + +private: + //============================================================================== + class NativeIterator + { + public: + NativeIterator (const File& directory, const String& wildCard); + ~NativeIterator(); + + bool next (String& filenameFound, + bool* isDirectory, bool* isHidden, int64* fileSize, + Time* modTime, Time* creationTime, bool* isReadOnly); + + class Pimpl; + + private: + friend class DirectoryIterator; + friend class ScopedPointer; + ScopedPointer pimpl; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeIterator) + }; + + friend class ScopedPointer; + StringArray wildCards; + NativeIterator fileFinder; + String wildCard, path; + int index; + mutable int totalNumFiles; + const int whatToLookFor; + const bool isRecursive; + bool hasBeenAdvanced; + ScopedPointer subIterator; + File currentFile; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryIterator) +}; + +#endif // BEAST_DIRECTORYITERATOR_BEASTHEADER diff --git a/modules/beast_core/files/beast_File.cpp b/modules/beast_core/files/beast_File.cpp new file mode 100644 index 0000000000..a4235f438c --- /dev/null +++ b/modules/beast_core/files/beast_File.cpp @@ -0,0 +1,1086 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +File::File (const String& fullPathName) + : fullPath (parseAbsolutePath (fullPathName)) +{ +} + +File File::createFileWithoutCheckingPath (const String& path) noexcept +{ + File f; + f.fullPath = path; + return f; +} + +File::File (const File& other) + : fullPath (other.fullPath) +{ +} + +File& File::operator= (const String& newPath) +{ + fullPath = parseAbsolutePath (newPath); + return *this; +} + +File& File::operator= (const File& other) +{ + fullPath = other.fullPath; + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +File::File (File&& other) noexcept + : fullPath (static_cast (other.fullPath)) +{ +} + +File& File::operator= (File&& other) noexcept +{ + fullPath = static_cast (other.fullPath); + return *this; +} +#endif + +const File File::nonexistent; + + +//============================================================================== +String File::parseAbsolutePath (const String& p) +{ + if (p.isEmpty()) + return String::empty; + +#if BEAST_WINDOWS + // Windows.. + String path (p.replaceCharacter ('/', '\\')); + + if (path.startsWithChar (separator)) + { + if (path[1] != separator) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; + } + } + else if (! path.containsChar (':')) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); + } +#else + // Mac or Linux.. + + // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here + // to catch anyone who's trying to run code that was written on Windows with hard-coded path names. + // If that's why you've ended up here, use File::getChildFile() to build your paths instead. + bassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\'))); + + String path (p); + + if (path.startsWithChar ('~')) + { + if (path[1] == separator || path[1] == 0) + { + // expand a name of the form "~/abc" + path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName() + + path.substring (1); + } + else + { + // expand a name of type "~dave/abc" + const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false)); + + if (struct passwd* const pw = getpwnam (userName.toUTF8())) + path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false); + } + } + else if (! path.startsWithChar (separator)) + { + #if BEAST_DEBUG || BEAST_LOG_ASSERTIONS + if (! (path.startsWith ("./") || path.startsWith ("../"))) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + #if BEAST_LOG_ASSERTIONS + Logger::writeToLog ("Illegal absolute path: " + path); + #endif + } + #endif + + return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); + } +#endif + + while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string. + path = path.dropLastCharacters (1); + + return path; +} + +String File::addTrailingSeparator (const String& path) +{ + return path.endsWithChar (separator) ? path + : path + separator; +} + +//============================================================================== +#if BEAST_LINUX + #define NAMES_ARE_CASE_SENSITIVE 1 +#endif + +bool File::areFileNamesCaseSensitive() +{ + #if NAMES_ARE_CASE_SENSITIVE + return true; + #else + return false; + #endif +} + +static int compareFilenames (const String& name1, const String& name2) noexcept +{ + #if NAMES_ARE_CASE_SENSITIVE + return name1.compare (name2); + #else + return name1.compareIgnoreCase (name2); + #endif +} + +bool File::operator== (const File& other) const { return compareFilenames (fullPath, other.fullPath) == 0; } +bool File::operator!= (const File& other) const { return compareFilenames (fullPath, other.fullPath) != 0; } +bool File::operator< (const File& other) const { return compareFilenames (fullPath, other.fullPath) < 0; } +bool File::operator> (const File& other) const { return compareFilenames (fullPath, other.fullPath) > 0; } + +//============================================================================== +bool File::setReadOnly (const bool shouldBeReadOnly, + const bool applyRecursively) const +{ + bool worked = true; + + if (applyRecursively && isDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (int i = subFiles.size(); --i >= 0;) + worked = subFiles.getReference(i).setReadOnly (shouldBeReadOnly, true) && worked; + } + + return setFileReadOnlyInternal (shouldBeReadOnly) && worked; +} + +bool File::deleteRecursively() const +{ + bool worked = true; + + if (isDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (int i = subFiles.size(); --i >= 0;) + worked = subFiles.getReference(i).deleteRecursively() && worked; + } + + return deleteFile() && worked; +} + +bool File::moveFileTo (const File& newFile) const +{ + if (newFile.fullPath == fullPath) + return true; + + if (! exists()) + return false; + + #if ! NAMES_ARE_CASE_SENSITIVE + if (*this != newFile) + #endif + if (! newFile.deleteFile()) + return false; + + return moveInternal (newFile); +} + +bool File::copyFileTo (const File& newFile) const +{ + return (*this == newFile) + || (exists() && newFile.deleteFile() && copyInternal (newFile)); +} + +bool File::copyDirectoryTo (const File& newDirectory) const +{ + if (isDirectory() && newDirectory.createDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFiles, false); + + for (int i = 0; i < subFiles.size(); ++i) + if (! subFiles.getReference(i).copyFileTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) + return false; + + subFiles.clear(); + findChildFiles (subFiles, File::findDirectories, false); + + for (int i = 0; i < subFiles.size(); ++i) + if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) + return false; + + return true; + } + + return false; +} + +//============================================================================== +String File::getPathUpToLastSlash() const +{ + const int lastSlash = fullPath.lastIndexOfChar (separator); + + if (lastSlash > 0) + return fullPath.substring (0, lastSlash); + + if (lastSlash == 0) + return separatorString; + + return fullPath; +} + +File File::getParentDirectory() const +{ + File f; + f.fullPath = getPathUpToLastSlash(); + return f; +} + +//============================================================================== +String File::getFileName() const +{ + return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1); +} + +String File::getFileNameWithoutExtension() const +{ + const int lastSlash = fullPath.lastIndexOfChar (separator) + 1; + const int lastDot = fullPath.lastIndexOfChar ('.'); + + if (lastDot > lastSlash) + return fullPath.substring (lastSlash, lastDot); + + return fullPath.substring (lastSlash); +} + +bool File::isAChildOf (const File& potentialParent) const +{ + if (potentialParent == File::nonexistent) + return false; + + const String ourPath (getPathUpToLastSlash()); + + if (compareFilenames (potentialParent.fullPath, ourPath) == 0) + return true; + + if (potentialParent.fullPath.length() >= ourPath.length()) + return false; + + return getParentDirectory().isAChildOf (potentialParent); +} + +int File::hashCode() const { return fullPath.hashCode(); } +int64 File::hashCode64() const { return fullPath.hashCode64(); } + +//============================================================================== +bool File::isAbsolutePath (const String& path) +{ + return path.startsWithChar (separator) + #if BEAST_WINDOWS + || (path.isNotEmpty() && path[1] == ':'); + #else + || path.startsWithChar ('~'); + #endif +} + +File File::getChildFile (String relativePath) const +{ + if (isAbsolutePath (relativePath)) + return File (relativePath); + + String path (fullPath); + + // It's relative, so remove any ../ or ./ bits at the start.. + if (relativePath[0] == '.') + { + #if BEAST_WINDOWS + relativePath = relativePath.replaceCharacter ('/', '\\'); + #endif + + while (relativePath[0] == '.') + { + const beast_wchar secondChar = relativePath[1]; + + if (secondChar == '.') + { + const beast_wchar thirdChar = relativePath[2]; + + if (thirdChar == 0 || thirdChar == separator) + { + const int lastSlash = path.lastIndexOfChar (separator); + if (lastSlash >= 0) + path = path.substring (0, lastSlash); + + relativePath = relativePath.substring (3); + } + else + { + break; + } + } + else if (secondChar == separator) + { + relativePath = relativePath.substring (2); + } + else + { + break; + } + } + } + + return File (addTrailingSeparator (path) + relativePath); +} + +File File::getSiblingFile (const String& fileName) const +{ + return getParentDirectory().getChildFile (fileName); +} + +//============================================================================== +String File::descriptionOfSizeInBytes (const int64 bytes) +{ + const char* suffix; + double divisor = 0; + + if (bytes == 1) { suffix = " byte"; } + else if (bytes < 1024) { suffix = " bytes"; } + else if (bytes < 1024 * 1024) { suffix = " KB"; divisor = 1024.0; } + else if (bytes < 1024 * 1024 * 1024) { suffix = " MB"; divisor = 1024.0 * 1024.0; } + else { suffix = " GB"; divisor = 1024.0 * 1024.0 * 1024.0; } + + return (divisor > 0 ? String (bytes / divisor, 1) : String (bytes)) + suffix; +} + +//============================================================================== +Result File::create() const +{ + if (exists()) + return Result::ok(); + + const File parentDir (getParentDirectory()); + + if (parentDir == *this) + return Result::fail ("Cannot create parent directory"); + + Result r (parentDir.createDirectory()); + + if (r.wasOk()) + { + FileOutputStream fo (*this, 8); + r = fo.getStatus(); + } + + return r; +} + +Result File::createDirectory() const +{ + if (isDirectory()) + return Result::ok(); + + const File parentDir (getParentDirectory()); + + if (parentDir == *this) + return Result::fail ("Cannot create parent directory"); + + Result r (parentDir.createDirectory()); + + if (r.wasOk()) + r = createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString)); + + return r; +} + +//============================================================================== +Time File::getLastModificationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); } +Time File::getLastAccessTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); } +Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); } + +bool File::setLastModificationTime (Time t) const { return setFileTimesInternal (t.toMilliseconds(), 0, 0); } +bool File::setLastAccessTime (Time t) const { return setFileTimesInternal (0, t.toMilliseconds(), 0); } +bool File::setCreationTime (Time t) const { return setFileTimesInternal (0, 0, t.toMilliseconds()); } + +//============================================================================== +bool File::loadFileAsData (MemoryBlock& destBlock) const +{ + if (! existsAsFile()) + return false; + + FileInputStream in (*this); + return in.openedOk() && getSize() == in.readIntoMemoryBlock (destBlock); +} + +String File::loadFileAsString() const +{ + if (! existsAsFile()) + return String::empty; + + FileInputStream in (*this); + return in.openedOk() ? in.readEntireStreamAsString() + : String::empty; +} + +void File::readLines (StringArray& destLines) const +{ + destLines.addLines (loadFileAsString()); +} + +//============================================================================== +int File::findChildFiles (Array& results, + const int whatToLookFor, + const bool searchRecursively, + const String& wildCardPattern) const +{ + DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); + + int total = 0; + while (di.next()) + { + results.add (di.getFile()); + ++total; + } + + return total; +} + +int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const +{ + DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor); + + int total = 0; + while (di.next()) + ++total; + + return total; +} + +bool File::containsSubDirectories() const +{ + if (! isDirectory()) + return false; + + DirectoryIterator di (*this, false, "*", findDirectories); + return di.next(); +} + +//============================================================================== +File File::getNonexistentChildFile (const String& suggestedPrefix, + const String& suffix, + bool putNumbersInBrackets) const +{ + File f (getChildFile (suggestedPrefix + suffix)); + + if (f.exists()) + { + int number = 1; + String prefix (suggestedPrefix); + + // remove any bracketed numbers that may already be on the end.. + if (prefix.trim().endsWithChar (')')) + { + putNumbersInBrackets = true; + + const int openBracks = prefix.lastIndexOfChar ('('); + const int closeBracks = prefix.lastIndexOfChar (')'); + + if (openBracks > 0 + && closeBracks > openBracks + && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789")) + { + number = prefix.substring (openBracks + 1, closeBracks).getIntValue(); + prefix = prefix.substring (0, openBracks); + } + } + + // also use brackets if it ends in a digit. + putNumbersInBrackets = putNumbersInBrackets + || CharacterFunctions::isDigit (prefix.getLastCharacter()); + + do + { + String newName (prefix); + + if (putNumbersInBrackets) + newName << '(' << ++number << ')'; + else + newName << ++number; + + f = getChildFile (newName + suffix); + + } while (f.exists()); + } + + return f; +} + +File File::getNonexistentSibling (const bool putNumbersInBrackets) const +{ + if (! exists()) + return *this; + + return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(), + getFileExtension(), + putNumbersInBrackets); +} + +//============================================================================== +String File::getFileExtension() const +{ + const int indexOfDot = fullPath.lastIndexOfChar ('.'); + + if (indexOfDot > fullPath.lastIndexOfChar (separator)) + return fullPath.substring (indexOfDot); + + return String::empty; +} + +bool File::hasFileExtension (const String& possibleSuffix) const +{ + if (possibleSuffix.isEmpty()) + return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator); + + const int semicolon = possibleSuffix.indexOfChar (0, ';'); + + if (semicolon >= 0) + { + return hasFileExtension (possibleSuffix.substring (0, semicolon).trimEnd()) + || hasFileExtension (possibleSuffix.substring (semicolon + 1).trimStart()); + } + else + { + if (fullPath.endsWithIgnoreCase (possibleSuffix)) + { + if (possibleSuffix.startsWithChar ('.')) + return true; + + const int dotPos = fullPath.length() - possibleSuffix.length() - 1; + + if (dotPos >= 0) + return fullPath [dotPos] == '.'; + } + } + + return false; +} + +File File::withFileExtension (const String& newExtension) const +{ + if (fullPath.isEmpty()) + return File::nonexistent; + + String filePart (getFileName()); + + const int i = filePart.lastIndexOfChar ('.'); + if (i >= 0) + filePart = filePart.substring (0, i); + + if (newExtension.isNotEmpty() && ! newExtension.startsWithChar ('.')) + filePart << '.'; + + return getSiblingFile (filePart + newExtension); +} + +//============================================================================== +bool File::startAsProcess (const String& parameters) const +{ + return exists() && Process::openDocument (fullPath, parameters); +} + +//============================================================================== +FileInputStream* File::createInputStream() const +{ + ScopedPointer fin (new FileInputStream (*this)); + + if (fin->openedOk()) + return fin.release(); + + return nullptr; +} + +FileOutputStream* File::createOutputStream (const int bufferSize) const +{ + ScopedPointer out (new FileOutputStream (*this, bufferSize)); + + return out->failedToOpen() ? nullptr + : out.release(); +} + +//============================================================================== +bool File::appendData (const void* const dataToAppend, + const size_t numberOfBytes) const +{ + bassert (((ssize_t) numberOfBytes) >= 0); + + if (numberOfBytes == 0) + return true; + + FileOutputStream out (*this, 8192); + return out.openedOk() && out.write (dataToAppend, numberOfBytes); +} + +bool File::replaceWithData (const void* const dataToWrite, + const size_t numberOfBytes) const +{ + if (numberOfBytes == 0) + return deleteFile(); + + TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); + tempFile.getFile().appendData (dataToWrite, numberOfBytes); + return tempFile.overwriteTargetFileWithTemporary(); +} + +bool File::appendText (const String& text, + const bool asUnicode, + const bool writeUnicodeHeaderBytes) const +{ + FileOutputStream out (*this); + + if (out.failedToOpen()) + return false; + + out.writeText (text, asUnicode, writeUnicodeHeaderBytes); + return true; +} + +bool File::replaceWithText (const String& textToWrite, + const bool asUnicode, + const bool writeUnicodeHeaderBytes) const +{ + TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); + tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes); + return tempFile.overwriteTargetFileWithTemporary(); +} + +bool File::hasIdenticalContentTo (const File& other) const +{ + if (other == *this) + return true; + + if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile()) + { + FileInputStream in1 (*this), in2 (other); + + if (in1.openedOk() && in2.openedOk()) + { + const int bufferSize = 4096; + HeapBlock buffer1 (bufferSize), buffer2 (bufferSize); + + for (;;) + { + const int num1 = in1.read (buffer1, bufferSize); + const int num2 = in2.read (buffer2, bufferSize); + + if (num1 != num2) + break; + + if (num1 <= 0) + return true; + + if (memcmp (buffer1, buffer2, (size_t) num1) != 0) + break; + } + } + } + + return false; +} + +//============================================================================== +String File::createLegalPathName (const String& original) +{ + String s (original); + String start; + + if (s[1] == ':') + { + start = s.substring (0, 2); + s = s.substring (2); + } + + return start + s.removeCharacters ("\"#@,;:<>*^|?") + .substring (0, 1024); +} + +String File::createLegalFileName (const String& original) +{ + String s (original.removeCharacters ("\"#@,;:<>*^|?\\/")); + + const int maxLength = 128; // only the length of the filename, not the whole path + const int len = s.length(); + + if (len > maxLength) + { + const int lastDot = s.lastIndexOfChar ('.'); + + if (lastDot > bmax (0, len - 12)) + { + s = s.substring (0, maxLength - (len - lastDot)) + + s.substring (lastDot); + } + else + { + s = s.substring (0, maxLength); + } + } + + return s; +} + +//============================================================================== +static int countNumberOfSeparators (String::CharPointerType s) +{ + int num = 0; + + for (;;) + { + const beast_wchar c = s.getAndAdvance(); + + if (c == 0) + break; + + if (c == File::separator) + ++num; + } + + return num; +} + +String File::getRelativePathFrom (const File& dir) const +{ + String thisPath (fullPath); + + while (thisPath.endsWithChar (separator)) + thisPath = thisPath.dropLastCharacters (1); + + String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName() + : dir.fullPath)); + + int commonBitLength = 0; + String::CharPointerType thisPathAfterCommon (thisPath.getCharPointer()); + String::CharPointerType dirPathAfterCommon (dirPath.getCharPointer()); + + { + String::CharPointerType thisPathIter (thisPath.getCharPointer()); + String::CharPointerType dirPathIter (dirPath.getCharPointer()); + + for (int i = 0;;) + { + const beast_wchar c1 = thisPathIter.getAndAdvance(); + const beast_wchar c2 = dirPathIter.getAndAdvance(); + + #if NAMES_ARE_CASE_SENSITIVE + if (c1 != c2 + #else + if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2)) + #endif + || c1 == 0) + break; + + ++i; + + if (c1 == separator) + { + thisPathAfterCommon = thisPathIter; + dirPathAfterCommon = dirPathIter; + commonBitLength = i; + } + } + } + + // if the only common bit is the root, then just return the full path.. + if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == separator)) + return fullPath; + + const int numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon); + + if (numUpDirectoriesNeeded == 0) + return thisPathAfterCommon; + + #if BEAST_WINDOWS + String s (String::repeatedString ("..\\", numUpDirectoriesNeeded)); + #else + String s (String::repeatedString ("../", numUpDirectoriesNeeded)); + #endif + s.appendCharPointer (thisPathAfterCommon); + return s; +} + +//============================================================================== +File File::createTempFile (const String& fileNameEnding) +{ + const File tempFile (getSpecialLocation (tempDirectory) + .getChildFile ("temp_" + String::toHexString (Random::getSystemRandom().nextInt())) + .withFileExtension (fileNameEnding)); + + if (tempFile.exists()) + return createTempFile (fileNameEnding); + + return tempFile; +} + +//============================================================================== +MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode) + : address (nullptr), range (0, file.getSize()), fileHandle (0) +{ + openInternal (file, mode); +} + +MemoryMappedFile::MemoryMappedFile (const File& file, const Range& fileRange, AccessMode mode) + : address (nullptr), range (fileRange.getIntersectionWith (Range (0, file.getSize()))), fileHandle (0) +{ + openInternal (file, mode); +} + + +//============================================================================== +#if BEAST_UNIT_TESTS + +class FileTests : public UnitTest +{ +public: + FileTests() : UnitTest ("Files") {} + + void runTest() + { + beginTest ("Reading"); + + const File home (File::getSpecialLocation (File::userHomeDirectory)); + const File temp (File::getSpecialLocation (File::tempDirectory)); + + expect (! File::nonexistent.exists()); + expect (home.isDirectory()); + expect (home.exists()); + expect (! home.existsAsFile()); + expect (File::getSpecialLocation (File::userDocumentsDirectory).isDirectory()); + expect (File::getSpecialLocation (File::userApplicationDataDirectory).isDirectory()); + expect (File::getSpecialLocation (File::currentExecutableFile).exists()); + expect (File::getSpecialLocation (File::currentApplicationFile).exists()); + expect (File::getSpecialLocation (File::invokedExecutableFile).exists()); + expect (home.getVolumeTotalSize() > 1024 * 1024); + expect (home.getBytesFreeOnVolume() > 0); + expect (! home.isHidden()); + expect (home.isOnHardDisk()); + expect (! home.isOnCDRomDrive()); + expect (File::getCurrentWorkingDirectory().exists()); + expect (home.setAsCurrentWorkingDirectory()); + expect (File::getCurrentWorkingDirectory() == home); + + { + Array roots; + File::findFileSystemRoots (roots); + expect (roots.size() > 0); + + int numRootsExisting = 0; + for (int i = 0; i < roots.size(); ++i) + if (roots[i].exists()) + ++numRootsExisting; + + // (on windows, some of the drives may not contain media, so as long as at least one is ok..) + expect (numRootsExisting > 0); + } + + beginTest ("Writing"); + + File demoFolder (temp.getChildFile ("Beast UnitTests Temp Folder.folder")); + expect (demoFolder.deleteRecursively()); + expect (demoFolder.createDirectory()); + expect (demoFolder.isDirectory()); + expect (demoFolder.getParentDirectory() == temp); + expect (temp.isDirectory()); + + { + Array files; + temp.findChildFiles (files, File::findFilesAndDirectories, false, "*"); + expect (files.contains (demoFolder)); + } + + { + Array files; + temp.findChildFiles (files, File::findDirectories, true, "*.folder"); + expect (files.contains (demoFolder)); + } + + File tempFile (demoFolder.getNonexistentChildFile ("test", ".txt", false)); + + expect (tempFile.getFileExtension() == ".txt"); + expect (tempFile.hasFileExtension (".txt")); + expect (tempFile.hasFileExtension ("txt")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension (".xyz")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension ("abc;xyz;foo")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension ("xyz;foo")); + expect (! tempFile.withFileExtension ("h").hasFileExtension ("bar;foo;xx")); + expect (tempFile.getSiblingFile ("foo").isAChildOf (temp)); + expect (tempFile.hasWriteAccess()); + + { + FileOutputStream fo (tempFile); + fo.write ("0123456789", 10); + } + + expect (tempFile.exists()); + expect (tempFile.getSize() == 10); + expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); + expectEquals (tempFile.loadFileAsString(), String ("0123456789")); + expect (! demoFolder.containsSubDirectories()); + + expectEquals (tempFile.getRelativePathFrom (demoFolder.getParentDirectory()), demoFolder.getFileName() + File::separatorString + tempFile.getFileName()); + expectEquals (demoFolder.getParentDirectory().getRelativePathFrom (tempFile), ".." + File::separatorString + ".." + File::separatorString + demoFolder.getParentDirectory().getFileName()); + + expect (demoFolder.getNumberOfChildFiles (File::findFiles) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 0); + demoFolder.getNonexistentChildFile ("tempFolder", "", false).createDirectory(); + expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 2); + expect (demoFolder.containsSubDirectories()); + + expect (tempFile.hasWriteAccess()); + tempFile.setReadOnly (true); + expect (! tempFile.hasWriteAccess()); + tempFile.setReadOnly (false); + expect (tempFile.hasWriteAccess()); + + Time t (Time::getCurrentTime()); + tempFile.setLastModificationTime (t); + Time t2 = tempFile.getLastModificationTime(); + expect (std::abs ((int) (t2.toMilliseconds() - t.toMilliseconds())) <= 1000); + + { + MemoryBlock mb; + tempFile.loadFileAsData (mb); + expect (mb.getSize() == 10); + expect (mb[0] == '0'); + } + + { + expect (tempFile.getSize() == 10); + FileOutputStream fo (tempFile); + expect (fo.openedOk()); + + expect (fo.setPosition (7)); + expect (fo.truncate().wasOk()); + expect (tempFile.getSize() == 7); + fo.write ("789", 3); + fo.flush(); + expect (tempFile.getSize() == 10); + } + + beginTest ("Memory-mapped files"); + + { + MemoryMappedFile mmf (tempFile, MemoryMappedFile::readOnly); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + expect (memcmp (mmf.getData(), "0123456789", 10) == 0); + } + + { + const File tempFile2 (tempFile.getNonexistentSibling (false)); + expect (tempFile2.create()); + expect (tempFile2.appendData ("xxxxxxxxxx", 10)); + + { + MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + memcpy (mmf.getData(), "abcdefghij", 10); + } + + { + MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + expect (memcmp (mmf.getData(), "abcdefghij", 10) == 0); + } + + expect (tempFile2.deleteFile()); + } + + beginTest ("More writing"); + + expect (tempFile.appendData ("abcdefghij", 10)); + expect (tempFile.getSize() == 20); + expect (tempFile.replaceWithData ("abcdefghij", 10)); + expect (tempFile.getSize() == 10); + + File tempFile2 (tempFile.getNonexistentSibling (false)); + expect (tempFile.copyFileTo (tempFile2)); + expect (tempFile2.exists()); + expect (tempFile2.hasIdenticalContentTo (tempFile)); + expect (tempFile.deleteFile()); + expect (! tempFile.exists()); + expect (tempFile2.moveFileTo (tempFile)); + expect (tempFile.exists()); + expect (! tempFile2.exists()); + + expect (demoFolder.deleteRecursively()); + expect (! demoFolder.exists()); + } +}; + +static FileTests fileUnitTests; + +#endif diff --git a/modules/beast_core/files/beast_File.h b/modules/beast_core/files/beast_File.h new file mode 100644 index 0000000000..462aeb58a9 --- /dev/null +++ b/modules/beast_core/files/beast_File.h @@ -0,0 +1,955 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_FILE_BEASTHEADER +#define BEAST_FILE_BEASTHEADER + +#include "../containers/beast_Array.h" +#include "../time/beast_Time.h" +#include "../text/beast_StringArray.h" +#include "../memory/beast_MemoryBlock.h" +#include "../memory/beast_ScopedPointer.h" +#include "../misc/beast_Result.h" +class FileInputStream; +class FileOutputStream; + + +//============================================================================== +/** + Represents a local file or directory. + + This class encapsulates the absolute pathname of a file or directory, and + has methods for finding out about the file and changing its properties. + + To read or write to the file, there are methods for returning an input or + output stream. + + @see FileInputStream, FileOutputStream +*/ +class BEAST_API File +{ +public: + //============================================================================== + /** Creates an (invalid) file object. + + The file is initially set to an empty path, so getFullPath() will return + an empty string, and comparing the file to File::nonexistent will return + true. + + You can use its operator= method to point it at a proper file. + */ + File() noexcept {} + + /** Creates a file from an absolute path. + + If the path supplied is a relative path, it is taken to be relative + to the current working directory (see File::getCurrentWorkingDirectory()), + but this isn't a recommended way of creating a file, because you + never know what the CWD is going to be. + + On the Mac/Linux, the path can include "~" notation for referring to + user home directories. + */ + File (const String& path); + + /** Creates a copy of another file object. */ + File (const File& other); + + /** Destructor. */ + ~File() noexcept {} + + /** Sets the file based on an absolute pathname. + + If the path supplied is a relative path, it is taken to be relative + to the current working directory (see File::getCurrentWorkingDirectory()), + but this isn't a recommended way of creating a file, because you + never know what the CWD is going to be. + + On the Mac/Linux, the path can include "~" notation for referring to + user home directories. + */ + File& operator= (const String& newFilePath); + + /** Copies from another file object. */ + File& operator= (const File& otherFile); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + File (File&& otherFile) noexcept; + File& operator= (File&& otherFile) noexcept; + #endif + + //============================================================================== + /** This static constant is used for referring to an 'invalid' file. */ + static const File nonexistent; + + //============================================================================== + /** Checks whether the file actually exists. + + @returns true if the file exists, either as a file or a directory. + @see existsAsFile, isDirectory + */ + bool exists() const; + + /** Checks whether the file exists and is a file rather than a directory. + + @returns true only if this is a real file, false if it's a directory + or doesn't exist + @see exists, isDirectory + */ + bool existsAsFile() const; + + /** Checks whether the file is a directory that exists. + + @returns true only if the file is a directory which actually exists, so + false if it's a file or doesn't exist at all + @see exists, existsAsFile + */ + bool isDirectory() const; + + /** Returns the size of the file in bytes. + + @returns the number of bytes in the file, or 0 if it doesn't exist. + */ + int64 getSize() const; + + /** Utility function to convert a file size in bytes to a neat string description. + + So for example 100 would return "100 bytes", 2000 would return "2 KB", + 2000000 would produce "2 MB", etc. + */ + static String descriptionOfSizeInBytes (int64 bytes); + + //============================================================================== + /** Returns the complete, absolute path of this file. + + This includes the filename and all its parent folders. On Windows it'll + also include the drive letter prefix; on Mac or Linux it'll be a complete + path starting from the root folder. + + If you just want the file's name, you should use getFileName() or + getFileNameWithoutExtension(). + + @see getFileName, getRelativePathFrom + */ + const String& getFullPathName() const noexcept { return fullPath; } + + /** Returns the last section of the pathname. + + Returns just the final part of the path - e.g. if the whole path + is "/moose/fish/foo.txt" this will return "foo.txt". + + For a directory, it returns the final part of the path - e.g. for the + directory "/moose/fish" it'll return "fish". + + If the filename begins with a dot, it'll return the whole filename, e.g. for + "/moose/.fish", it'll return ".fish" + + @see getFullPathName, getFileNameWithoutExtension + */ + String getFileName() const; + + /** Creates a relative path that refers to a file relatively to a given directory. + + e.g. File ("/moose/foo.txt").getRelativePathFrom (File ("/moose/fish/haddock")) + would return "../../foo.txt". + + If it's not possible to navigate from one file to the other, an absolute + path is returned. If the paths are invalid, an empty string may also be + returned. + + @param directoryToBeRelativeTo the directory which the resultant string will + be relative to. If this is actually a file rather than + a directory, its parent directory will be used instead. + If it doesn't exist, it's assumed to be a directory. + @see getChildFile, isAbsolutePath + */ + String getRelativePathFrom (const File& directoryToBeRelativeTo) const; + + //============================================================================== + /** Returns the file's extension. + + Returns the file extension of this file, also including the dot. + + e.g. "/moose/fish/foo.txt" would return ".txt" + + @see hasFileExtension, withFileExtension, getFileNameWithoutExtension + */ + String getFileExtension() const; + + /** Checks whether the file has a given extension. + + @param extensionToTest the extension to look for - it doesn't matter whether or + not this string has a dot at the start, so ".wav" and "wav" + will have the same effect. The comparison used is + case-insensitve. To compare with multiple extensions, this + parameter can contain multiple strings, separated by semi-colons - + so, for example: hasFileExtension (".jpeg;png;gif") would return + true if the file has any of those three extensions. + + @see getFileExtension, withFileExtension, getFileNameWithoutExtension + */ + bool hasFileExtension (const String& extensionToTest) const; + + /** Returns a version of this file with a different file extension. + + e.g. File ("/moose/fish/foo.txt").withFileExtension ("html") returns "/moose/fish/foo.html" + + @param newExtension the new extension, either with or without a dot at the start (this + doesn't make any difference). To get remove a file's extension altogether, + pass an empty string into this function. + + @see getFileName, getFileExtension, hasFileExtension, getFileNameWithoutExtension + */ + File withFileExtension (const String& newExtension) const; + + /** Returns the last part of the filename, without its file extension. + + e.g. for "/moose/fish/foo.txt" this will return "foo". + + @see getFileName, getFileExtension, hasFileExtension, withFileExtension + */ + String getFileNameWithoutExtension() const; + + //============================================================================== + /** Returns a 32-bit hash-code that identifies this file. + + This is based on the filename. Obviously it's possible, although unlikely, that + two files will have the same hash-code. + */ + int hashCode() const; + + /** Returns a 64-bit hash-code that identifies this file. + + This is based on the filename. Obviously it's possible, although unlikely, that + two files will have the same hash-code. + */ + int64 hashCode64() const; + + //============================================================================== + /** Returns a file based on a relative path. + + This will find a child file or directory of the current object. + + e.g. + File ("/moose/fish").getChildFile ("foo.txt") will produce "/moose/fish/foo.txt". + File ("/moose/fish").getChildFile ("../foo.txt") will produce "/moose/foo.txt". + + If the string is actually an absolute path, it will be treated as such, e.g. + File ("/moose/fish").getChildFile ("/foo.txt") will produce "/foo.txt" + + @see getSiblingFile, getParentDirectory, getRelativePathFrom, isAChildOf + */ + File getChildFile (String relativePath) const; + + /** Returns a file which is in the same directory as this one. + + This is equivalent to getParentDirectory().getChildFile (name). + + @see getChildFile, getParentDirectory + */ + File getSiblingFile (const String& siblingFileName) const; + + //============================================================================== + /** Returns the directory that contains this file or directory. + + e.g. for "/moose/fish/foo.txt" this will return "/moose/fish". + */ + File getParentDirectory() const; + + /** Checks whether a file is somewhere inside a directory. + + Returns true if this file is somewhere inside a subdirectory of the directory + that is passed in. Neither file actually has to exist, because the function + just checks the paths for similarities. + + e.g. File ("/moose/fish/foo.txt").isAChildOf ("/moose") is true. + File ("/moose/fish/foo.txt").isAChildOf ("/moose/fish") is also true. + */ + bool isAChildOf (const File& potentialParentDirectory) const; + + //============================================================================== + /** Chooses a filename relative to this one that doesn't already exist. + + If this file is a directory, this will return a child file of this + directory that doesn't exist, by adding numbers to a prefix and suffix until + it finds one that isn't already there. + + If the prefix + the suffix doesn't exist, it won't bother adding a number. + + e.g. File ("/moose/fish").getNonexistentChildFile ("foo", ".txt", true) might + return "/moose/fish/foo(2).txt" if there's already a file called "foo.txt". + + @param prefix the string to use for the filename before the number + @param suffix the string to add to the filename after the number + @param putNumbersInBrackets if true, this will create filenames in the + format "prefix(number)suffix", if false, it will leave the + brackets out. + */ + File getNonexistentChildFile (const String& prefix, + const String& suffix, + bool putNumbersInBrackets = true) const; + + /** Chooses a filename for a sibling file to this one that doesn't already exist. + + If this file doesn't exist, this will just return itself, otherwise it + will return an appropriate sibling that doesn't exist, e.g. if a file + "/moose/fish/foo.txt" exists, this might return "/moose/fish/foo(2).txt". + + @param putNumbersInBrackets whether to add brackets around the numbers that + get appended to the new filename. + */ + File getNonexistentSibling (bool putNumbersInBrackets = true) const; + + //============================================================================== + /** Compares the pathnames for two files. */ + bool operator== (const File& otherFile) const; + /** Compares the pathnames for two files. */ + bool operator!= (const File& otherFile) const; + /** Compares the pathnames for two files. */ + bool operator< (const File& otherFile) const; + /** Compares the pathnames for two files. */ + bool operator> (const File& otherFile) const; + + //============================================================================== + /** Checks whether a file can be created or written to. + + @returns true if it's possible to create and write to this file. If the file + doesn't already exist, this will check its parent directory to + see if writing is allowed. + @see setReadOnly + */ + bool hasWriteAccess() const; + + /** Changes the write-permission of a file or directory. + + @param shouldBeReadOnly whether to add or remove write-permission + @param applyRecursively if the file is a directory and this is true, it will + recurse through all the subfolders changing the permissions + of all files + @returns true if it manages to change the file's permissions. + @see hasWriteAccess + */ + bool setReadOnly (bool shouldBeReadOnly, + bool applyRecursively = false) const; + + /** Returns true if this file is a hidden or system file. + The criteria for deciding whether a file is hidden are platform-dependent. + */ + bool isHidden() const; + + /** If this file is a link, this returns the file that it points to. + If this file isn't actually link, it'll just return itself. + */ + File getLinkedTarget() const; + + //============================================================================== + /** Returns the last modification time of this file. + + @returns the time, or an invalid time if the file doesn't exist. + @see setLastModificationTime, getLastAccessTime, getCreationTime + */ + Time getLastModificationTime() const; + + /** Returns the last time this file was accessed. + + @returns the time, or an invalid time if the file doesn't exist. + @see setLastAccessTime, getLastModificationTime, getCreationTime + */ + Time getLastAccessTime() const; + + /** Returns the time that this file was created. + + @returns the time, or an invalid time if the file doesn't exist. + @see getLastModificationTime, getLastAccessTime + */ + Time getCreationTime() const; + + /** Changes the modification time for this file. + + @param newTime the time to apply to the file + @returns true if it manages to change the file's time. + @see getLastModificationTime, setLastAccessTime, setCreationTime + */ + bool setLastModificationTime (Time newTime) const; + + /** Changes the last-access time for this file. + + @param newTime the time to apply to the file + @returns true if it manages to change the file's time. + @see getLastAccessTime, setLastModificationTime, setCreationTime + */ + bool setLastAccessTime (Time newTime) const; + + /** Changes the creation date for this file. + + @param newTime the time to apply to the file + @returns true if it manages to change the file's time. + @see getCreationTime, setLastModificationTime, setLastAccessTime + */ + bool setCreationTime (Time newTime) const; + + /** If possible, this will try to create a version string for the given file. + + The OS may be able to look at the file and give a version for it - e.g. with + executables, bundles, dlls, etc. If no version is available, this will + return an empty string. + */ + String getVersion() const; + + //============================================================================== + /** Creates an empty file if it doesn't already exist. + + If the file that this object refers to doesn't exist, this will create a file + of zero size. + + If it already exists or is a directory, this method will do nothing. + + @returns true if the file has been created (or if it already existed). + @see createDirectory + */ + Result create() const; + + /** Creates a new directory for this filename. + + This will try to create the file as a directory, and fill also create + any parent directories it needs in order to complete the operation. + + @returns a result to indicate whether the directory was created successfully, or + an error message if it failed. + @see create + */ + Result createDirectory() const; + + /** Deletes a file. + + If this file is actually a directory, it may not be deleted correctly if it + contains files. See deleteRecursively() as a better way of deleting directories. + + @returns true if the file has been successfully deleted (or if it didn't exist to + begin with). + @see deleteRecursively + */ + bool deleteFile() const; + + /** Deletes a file or directory and all its subdirectories. + + If this file is a directory, this will try to delete it and all its subfolders. If + it's just a file, it will just try to delete the file. + + @returns true if the file and all its subfolders have been successfully deleted + (or if it didn't exist to begin with). + @see deleteFile + */ + bool deleteRecursively() const; + + /** Moves this file or folder to the trash. + + @returns true if the operation succeeded. It could fail if the trash is full, or + if the file is write-protected, so you should check the return value + and act appropriately. + */ + bool moveToTrash() const; + + /** Moves or renames a file. + + Tries to move a file to a different location. + If the target file already exists, this will attempt to delete it first, and + will fail if this can't be done. + + Note that the destination file isn't the directory to put it in, it's the actual + filename that you want the new file to have. + + @returns true if the operation succeeds + */ + bool moveFileTo (const File& targetLocation) const; + + /** Copies a file. + + Tries to copy a file to a different location. + If the target file already exists, this will attempt to delete it first, and + will fail if this can't be done. + + @returns true if the operation succeeds + */ + bool copyFileTo (const File& targetLocation) const; + + /** Copies a directory. + + Tries to copy an entire directory, recursively. + + If this file isn't a directory or if any target files can't be created, this + will return false. + + @param newDirectory the directory that this one should be copied to. Note that this + is the name of the actual directory to create, not the directory + into which the new one should be placed, so there must be enough + write privileges to create it if it doesn't exist. Any files inside + it will be overwritten by similarly named ones that are copied. + */ + bool copyDirectoryTo (const File& newDirectory) const; + + //============================================================================== + /** Used in file searching, to specify whether to return files, directories, or both. + */ + enum TypesOfFileToFind + { + findDirectories = 1, /**< Use this flag to indicate that you want to find directories. */ + findFiles = 2, /**< Use this flag to indicate that you want to find files. */ + findFilesAndDirectories = 3, /**< Use this flag to indicate that you want to find both files and directories. */ + ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */ + }; + + /** Searches inside a directory for files matching a wildcard pattern. + + Assuming that this file is a directory, this method will search it + for either files or subdirectories whose names match a filename pattern. + + @param results an array to which File objects will be added for the + files that the search comes up with + @param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to + return files, directories, or both. If the ignoreHiddenFiles flag + is also added to this value, hidden files won't be returned + @param searchRecursively if true, all subdirectories will be recursed into to do + an exhaustive search + @param wildCardPattern the filename pattern to search for, e.g. "*.txt" + @returns the number of results that have been found + + @see getNumberOfChildFiles, DirectoryIterator + */ + int findChildFiles (Array& results, + int whatToLookFor, + bool searchRecursively, + const String& wildCardPattern = "*") const; + + /** Searches inside a directory and counts how many files match a wildcard pattern. + + Assuming that this file is a directory, this method will search it + for either files or subdirectories whose names match a filename pattern, + and will return the number of matches found. + + This isn't a recursive call, and will only search this directory, not + its children. + + @param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to + count files, directories, or both. If the ignoreHiddenFiles flag + is also added to this value, hidden files won't be counted + @param wildCardPattern the filename pattern to search for, e.g. "*.txt" + @returns the number of matches found + @see findChildFiles, DirectoryIterator + */ + int getNumberOfChildFiles (int whatToLookFor, + const String& wildCardPattern = "*") const; + + /** Returns true if this file is a directory that contains one or more subdirectories. + @see isDirectory, findChildFiles + */ + bool containsSubDirectories() const; + + //============================================================================== + /** Creates a stream to read from this file. + + @returns a stream that will read from this file (initially positioned at the + start of the file), or nullptr if the file can't be opened for some reason + @see createOutputStream, loadFileAsData + */ + FileInputStream* createInputStream() const; + + /** Creates a stream to write to this file. + + If the file exists, the stream that is returned will be positioned ready for + writing at the end of the file, so you might want to use deleteFile() first + to write to an empty file. + + @returns a stream that will write to this file (initially positioned at the + end of the file), or nullptr if the file can't be opened for some reason + @see createInputStream, appendData, appendText + */ + FileOutputStream* createOutputStream (int bufferSize = 0x8000) const; + + //============================================================================== + /** Loads a file's contents into memory as a block of binary data. + + Of course, trying to load a very large file into memory will blow up, so + it's better to check first. + + @param result the data block to which the file's contents should be appended - note + that if the memory block might already contain some data, you + might want to clear it first + @returns true if the file could all be read into memory + */ + bool loadFileAsData (MemoryBlock& result) const; + + /** Reads a file into memory as a string. + + Attempts to load the entire file as a zero-terminated string. + + This makes use of InputStream::readEntireStreamAsString, which can + read either UTF-16 or UTF-8 file formats. + */ + String loadFileAsString() const; + + /** Reads the contents of this file as text and splits it into lines, which are + appended to the given StringArray. + */ + void readLines (StringArray& destLines) const; + + //============================================================================== + /** Appends a block of binary data to the end of the file. + + This will try to write the given buffer to the end of the file. + + @returns false if it can't write to the file for some reason + */ + bool appendData (const void* dataToAppend, + size_t numberOfBytes) const; + + /** Replaces this file's contents with a given block of data. + + This will delete the file and replace it with the given data. + + A nice feature of this method is that it's safe - instead of deleting + the file first and then re-writing it, it creates a new temporary file, + writes the data to that, and then moves the new file to replace the existing + file. This means that if the power gets pulled out or something crashes, + you're a lot less likely to end up with a corrupted or unfinished file.. + + Returns true if the operation succeeds, or false if it fails. + + @see appendText + */ + bool replaceWithData (const void* dataToWrite, + size_t numberOfBytes) const; + + /** Appends a string to the end of the file. + + This will try to append a text string to the file, as either 16-bit unicode + or 8-bit characters in the default system encoding. + + It can also write the 'ff fe' unicode header bytes before the text to indicate + the endianness of the file. + + Any single \\n characters in the string are replaced with \\r\\n before it is written. + + @see replaceWithText + */ + bool appendText (const String& textToAppend, + bool asUnicode = false, + bool writeUnicodeHeaderBytes = false) const; + + /** Replaces this file's contents with a given text string. + + This will delete the file and replace it with the given text. + + A nice feature of this method is that it's safe - instead of deleting + the file first and then re-writing it, it creates a new temporary file, + writes the text to that, and then moves the new file to replace the existing + file. This means that if the power gets pulled out or something crashes, + you're a lot less likely to end up with an empty file.. + + For an explanation of the parameters here, see the appendText() method. + + Returns true if the operation succeeds, or false if it fails. + + @see appendText + */ + bool replaceWithText (const String& textToWrite, + bool asUnicode = false, + bool writeUnicodeHeaderBytes = false) const; + + /** Attempts to scan the contents of this file and compare it to another file, returning + true if this is possible and they match byte-for-byte. + */ + bool hasIdenticalContentTo (const File& other) const; + + //============================================================================== + /** Creates a set of files to represent each file root. + + e.g. on Windows this will create files for "c:\", "d:\" etc according + to which ones are available. On the Mac/Linux, this will probably + just add a single entry for "/". + */ + static void findFileSystemRoots (Array& results); + + /** Finds the name of the drive on which this file lives. + @returns the volume label of the drive, or an empty string if this isn't possible + */ + String getVolumeLabel() const; + + /** Returns the serial number of the volume on which this file lives. + @returns the serial number, or zero if there's a problem doing this + */ + int getVolumeSerialNumber() const; + + /** Returns the number of bytes free on the drive that this file lives on. + + @returns the number of bytes free, or 0 if there's a problem finding this out + @see getVolumeTotalSize + */ + int64 getBytesFreeOnVolume() const; + + /** Returns the total size of the drive that contains this file. + + @returns the total number of bytes that the volume can hold + @see getBytesFreeOnVolume + */ + int64 getVolumeTotalSize() const; + + /** Returns true if this file is on a CD or DVD drive. */ + bool isOnCDRomDrive() const; + + /** Returns true if this file is on a hard disk. + + This will fail if it's a network drive, but will still be true for + removable hard-disks. + */ + bool isOnHardDisk() const; + + /** Returns true if this file is on a removable disk drive. + + This might be a usb-drive, a CD-rom, or maybe a network drive. + */ + bool isOnRemovableDrive() const; + + //============================================================================== + /** Launches the file as a process. + + - if the file is executable, this will run it. + + - if it's a document of some kind, it will launch the document with its + default viewer application. + + - if it's a folder, it will be opened in Explorer, Finder, or equivalent. + + @see revealToUser + */ + bool startAsProcess (const String& parameters = String::empty) const; + + /** Opens Finder, Explorer, or whatever the OS uses, to show the user this file's location. + @see startAsProcess + */ + void revealToUser() const; + + //============================================================================== + /** A set of types of location that can be passed to the getSpecialLocation() method. + */ + enum SpecialLocationType + { + /** The user's home folder. This is the same as using File ("~"). */ + userHomeDirectory, + + /** The user's default documents folder. On Windows, this might be the user's + "My Documents" folder. On the Mac it'll be their "Documents" folder. Linux + doesn't tend to have one of these, so it might just return their home folder. + */ + userDocumentsDirectory, + + /** The folder that contains the user's desktop objects. */ + userDesktopDirectory, + + /** The folder in which applications store their persistent user-specific settings. + On Windows, this might be "\Documents and Settings\username\Application Data". + On the Mac, it might be "~/Library". If you're going to store your settings in here, + always create your own sub-folder to put them in, to avoid making a mess. + */ + userApplicationDataDirectory, + + /** An equivalent of the userApplicationDataDirectory folder that is shared by all users + of the computer, rather than just the current user. + + On the Mac it'll be "/Library", on Windows, it could be something like + "\Documents and Settings\All Users\Application Data". + + Depending on the setup, this folder may be read-only. + */ + commonApplicationDataDirectory, + + /** The folder that should be used for temporary files. + Always delete them when you're finished, to keep the user's computer tidy! + */ + tempDirectory, + + /** Returns this application's executable file. + + If running as a plug-in or DLL, this will (where possible) be the DLL rather than the + host app. + + On the mac this will return the unix binary, not the package folder - see + currentApplicationFile for that. + + See also invokedExecutableFile, which is similar, but if the exe was launched from a + file link, invokedExecutableFile will return the name of the link. + */ + currentExecutableFile, + + /** Returns this application's location. + + If running as a plug-in or DLL, this will (where possible) be the DLL rather than the + host app. + + On the mac this will return the package folder (if it's in one), not the unix binary + that's inside it - compare with currentExecutableFile. + */ + currentApplicationFile, + + /** Returns the file that was invoked to launch this executable. + This may differ from currentExecutableFile if the app was started from e.g. a link - this + will return the name of the link that was used, whereas currentExecutableFile will return + the actual location of the target executable. + */ + invokedExecutableFile, + + /** In a plugin, this will return the path of the host executable. */ + hostApplicationPath, + + /** The directory in which applications normally get installed. + So on windows, this would be something like "c:\program files", on the + Mac "/Applications", or "/usr" on linux. + */ + globalApplicationsDirectory, + + /** The most likely place where a user might store their music files. */ + userMusicDirectory, + + /** The most likely place where a user might store their movie files. */ + userMoviesDirectory, + + /** The most likely place where a user might store their picture files. */ + userPicturesDirectory + }; + + /** Finds the location of a special type of file or directory, such as a home folder or + documents folder. + + @see SpecialLocationType + */ + static File BEAST_CALLTYPE getSpecialLocation (const SpecialLocationType type); + + //============================================================================== + /** Returns a temporary file in the system's temp directory. + This will try to return the name of a non-existent temp file. + To get the temp folder, you can use getSpecialLocation (File::tempDirectory). + */ + static File createTempFile (const String& fileNameEnding); + + + //============================================================================== + /** Returns the current working directory. + @see setAsCurrentWorkingDirectory + */ + static File getCurrentWorkingDirectory(); + + /** Sets the current working directory to be this file. + + For this to work the file must point to a valid directory. + + @returns true if the current directory has been changed. + @see getCurrentWorkingDirectory + */ + bool setAsCurrentWorkingDirectory() const; + + //============================================================================== + /** The system-specific file separator character. + On Windows, this will be '\', on Mac/Linux, it'll be '/' + */ + static const beast_wchar separator; + + /** The system-specific file separator character, as a string. + On Windows, this will be '\', on Mac/Linux, it'll be '/' + */ + static const String separatorString; + + //============================================================================== + /** Removes illegal characters from a filename. + + This will return a copy of the given string after removing characters + that are not allowed in a legal filename, and possibly shortening the + string if it's too long. + + Because this will remove slashes, don't use it on an absolute pathname. + + @see createLegalPathName + */ + static String createLegalFileName (const String& fileNameToFix); + + /** Removes illegal characters from a pathname. + + Similar to createLegalFileName(), but this won't remove slashes, so can + be used on a complete pathname. + + @see createLegalFileName + */ + static String createLegalPathName (const String& pathNameToFix); + + /** Indicates whether filenames are case-sensitive on the current operating system. */ + static bool areFileNamesCaseSensitive(); + + /** Returns true if the string seems to be a fully-specified absolute path. */ + static bool isAbsolutePath (const String& path); + + /** Creates a file that simply contains this string, without doing the sanity-checking + that the normal constructors do. + + Best to avoid this unless you really know what you're doing. + */ + static File createFileWithoutCheckingPath (const String& path) noexcept; + + /** Adds a separator character to the end of a path if it doesn't already have one. */ + static String addTrailingSeparator (const String& path); + + #if BEAST_MAC || BEAST_IOS || DOXYGEN + //============================================================================== + /** OSX ONLY - Finds the OSType of a file from the its resources. */ + OSType getMacOSType() const; + + /** OSX ONLY - Returns true if this file is actually a bundle. */ + bool isBundle() const; + #endif + + #if BEAST_MAC || DOXYGEN + /** OSX ONLY - Adds this file to the OSX dock */ + void addToDock() const; + #endif + + #if BEAST_WINDOWS + /** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ + bool createLink (const String& description, const File& linkFileToCreate) const; + #endif + +private: + //============================================================================== + String fullPath; + + static String parseAbsolutePath (const String&); + String getPathUpToLastSlash() const; + + Result createDirectoryInternal (const String&) const; + bool copyInternal (const File&) const; + bool moveInternal (const File&) const; + bool setFileTimesInternal (int64 m, int64 a, int64 c) const; + void getFileTimesInternal (int64& m, int64& a, int64& c) const; + bool setFileReadOnlyInternal (bool) const; + + BEAST_LEAK_DETECTOR (File) +}; + +#endif // BEAST_FILE_BEASTHEADER diff --git a/modules/beast_core/files/beast_FileInputStream.cpp b/modules/beast_core/files/beast_FileInputStream.cpp new file mode 100644 index 0000000000..bceed5ba2f --- /dev/null +++ b/modules/beast_core/files/beast_FileInputStream.cpp @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +int64 beast_fileSetPosition (void* handle, int64 pos); + +//============================================================================== +FileInputStream::FileInputStream (const File& f) + : file (f), + fileHandle (nullptr), + currentPosition (0), + status (Result::ok()), + needToSeek (true) +{ + openHandle(); +} + +FileInputStream::~FileInputStream() +{ + closeHandle(); +} + +//============================================================================== +int64 FileInputStream::getTotalLength() +{ + return file.getSize(); +} + +int FileInputStream::read (void* buffer, int bytesToRead) +{ + bassert (openedOk()); + bassert (buffer != nullptr && bytesToRead >= 0); + + if (needToSeek) + { + if (beast_fileSetPosition (fileHandle, currentPosition) < 0) + return 0; + + needToSeek = false; + } + + const size_t num = readInternal (buffer, (size_t) bytesToRead); + currentPosition += num; + + return (int) num; +} + +bool FileInputStream::isExhausted() +{ + return currentPosition >= getTotalLength(); +} + +int64 FileInputStream::getPosition() +{ + return currentPosition; +} + +bool FileInputStream::setPosition (int64 pos) +{ + bassert (openedOk()); + + if (pos != currentPosition) + { + pos = blimit ((int64) 0, getTotalLength(), pos); + + needToSeek |= (currentPosition != pos); + currentPosition = pos; + } + + return true; +} diff --git a/modules/beast_core/files/beast_FileInputStream.h b/modules/beast_core/files/beast_FileInputStream.h new file mode 100644 index 0000000000..72ee64f126 --- /dev/null +++ b/modules/beast_core/files/beast_FileInputStream.h @@ -0,0 +1,94 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_FILEINPUTSTREAM_BEASTHEADER +#define BEAST_FILEINPUTSTREAM_BEASTHEADER + +#include "beast_File.h" +#include "../streams/beast_InputStream.h" + + +//============================================================================== +/** + An input stream that reads from a local file. + + @see InputStream, FileOutputStream, File::createInputStream +*/ +class BEAST_API FileInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a FileInputStream. + + @param fileToRead the file to read from - if the file can't be accessed for some + reason, then the stream will just contain no data + */ + explicit FileInputStream (const File& fileToRead); + + /** Destructor. */ + ~FileInputStream(); + + //============================================================================== + /** Returns the file that this stream is reading from. */ + const File& getFile() const noexcept { return file; } + + /** Returns the status of the file stream. + The result will be ok if the file opened successfully. If an error occurs while + opening or reading from the file, this will contain an error message. + */ + const Result& getStatus() const noexcept { return status; } + + /** Returns true if the stream couldn't be opened for some reason. + @see getResult() + */ + bool failedToOpen() const noexcept { return status.failed(); } + + /** Returns true if the stream opened without problems. + @see getResult() + */ + bool openedOk() const noexcept { return status.wasOk(); } + + + //============================================================================== + int64 getTotalLength(); + int read (void* destBuffer, int maxBytesToRead); + bool isExhausted(); + int64 getPosition(); + bool setPosition (int64 pos); + +private: + //============================================================================== + File file; + void* fileHandle; + int64 currentPosition; + Result status; + bool needToSeek; + + void openHandle(); + void closeHandle(); + size_t readInternal (void* buffer, size_t numBytes); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) +}; + +#endif // BEAST_FILEINPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/files/beast_FileOutputStream.cpp b/modules/beast_core/files/beast_FileOutputStream.cpp new file mode 100644 index 0000000000..368eb9c438 --- /dev/null +++ b/modules/beast_core/files/beast_FileOutputStream.cpp @@ -0,0 +1,131 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +int64 beast_fileSetPosition (void* handle, int64 pos); + +//============================================================================== +FileOutputStream::FileOutputStream (const File& f, const int bufferSize_) + : file (f), + fileHandle (nullptr), + status (Result::ok()), + currentPosition (0), + bufferSize (bufferSize_), + bytesInBuffer (0), + buffer ((size_t) bmax (bufferSize_, 16)) +{ + openHandle(); +} + +FileOutputStream::~FileOutputStream() +{ + flushBuffer(); + flushInternal(); + closeHandle(); +} + +int64 FileOutputStream::getPosition() +{ + return currentPosition; +} + +bool FileOutputStream::setPosition (int64 newPosition) +{ + if (newPosition != currentPosition) + { + flushBuffer(); + currentPosition = beast_fileSetPosition (fileHandle, newPosition); + } + + return newPosition == currentPosition; +} + +bool FileOutputStream::flushBuffer() +{ + bool ok = true; + + if (bytesInBuffer > 0) + { + ok = (writeInternal (buffer, bytesInBuffer) == (ssize_t) bytesInBuffer); + bytesInBuffer = 0; + } + + return ok; +} + +void FileOutputStream::flush() +{ + flushBuffer(); + flushInternal(); +} + +bool FileOutputStream::write (const void* const src, const size_t numBytes) +{ + bassert (src != nullptr && ((ssize_t) numBytes) >= 0); + + if (bytesInBuffer + numBytes < bufferSize) + { + memcpy (buffer + bytesInBuffer, src, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + } + else + { + if (! flushBuffer()) + return false; + + if (numBytes < bufferSize) + { + memcpy (buffer + bytesInBuffer, src, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + } + else + { + const ssize_t bytesWritten = writeInternal (src, numBytes); + + if (bytesWritten < 0) + return false; + + currentPosition += bytesWritten; + return bytesWritten == (ssize_t) numBytes; + } + } + + return true; +} + +void FileOutputStream::writeRepeatedByte (uint8 byte, size_t numBytes) +{ + bassert (((ssize_t) numBytes) >= 0); + + if (bytesInBuffer + numBytes < bufferSize) + { + memset (buffer + bytesInBuffer, byte, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + } + else + { + OutputStream::writeRepeatedByte (byte, numBytes); + } +} diff --git a/modules/beast_core/files/beast_FileOutputStream.h b/modules/beast_core/files/beast_FileOutputStream.h new file mode 100644 index 0000000000..a51d806c51 --- /dev/null +++ b/modules/beast_core/files/beast_FileOutputStream.h @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_FILEOUTPUTSTREAM_BEASTHEADER +#define BEAST_FILEOUTPUTSTREAM_BEASTHEADER + +#include "beast_File.h" +#include "../streams/beast_OutputStream.h" + + +//============================================================================== +/** + An output stream that writes into a local file. + + @see OutputStream, FileInputStream, File::createOutputStream +*/ +class BEAST_API FileOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates a FileOutputStream. + + If the file doesn't exist, it will first be created. If the file can't be + created or opened, the failedToOpen() method will return + true. + + If the file already exists when opened, the stream's write-postion will + be set to the end of the file. To overwrite an existing file, + use File::deleteFile() before opening the stream, or use setPosition(0) + after it's opened (although this won't truncate the file). + + @see TemporaryFile + */ + FileOutputStream (const File& fileToWriteTo, + int bufferSizeToUse = 16384); + + /** Destructor. */ + ~FileOutputStream(); + + //============================================================================== + /** Returns the file that this stream is writing to. + */ + const File& getFile() const { return file; } + + /** Returns the status of the file stream. + The result will be ok if the file opened successfully. If an error occurs while + opening or writing to the file, this will contain an error message. + */ + const Result& getStatus() const noexcept { return status; } + + /** Returns true if the stream couldn't be opened for some reason. + @see getResult() + */ + bool failedToOpen() const noexcept { return status.failed(); } + + /** Returns true if the stream opened without problems. + @see getResult() + */ + bool openedOk() const noexcept { return status.wasOk(); } + + /** Attempts to truncate the file to the current write position. + To truncate a file to a specific size, first use setPosition() to seek to the + appropriate location, and then call this method. + */ + Result truncate(); + + //============================================================================== + void flush(); + int64 getPosition(); + bool setPosition (int64 pos); + bool write (const void* data, size_t numBytes); + void writeRepeatedByte (uint8 byte, size_t numTimesToRepeat); + + +private: + //============================================================================== + File file; + void* fileHandle; + Result status; + int64 currentPosition; + size_t bufferSize, bytesInBuffer; + HeapBlock buffer; + + void openHandle(); + void closeHandle(); + void flushInternal(); + bool flushBuffer(); + int64 setPositionInternal (int64); + ssize_t writeInternal (const void*, size_t); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileOutputStream) +}; + +#endif // BEAST_FILEOUTPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/files/beast_FileSearchPath.cpp b/modules/beast_core/files/beast_FileSearchPath.cpp new file mode 100644 index 0000000000..bfef91dd77 --- /dev/null +++ b/modules/beast_core/files/beast_FileSearchPath.cpp @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +FileSearchPath::FileSearchPath() +{ +} + +FileSearchPath::FileSearchPath (const String& path) +{ + init (path); +} + +FileSearchPath::FileSearchPath (const FileSearchPath& other) + : directories (other.directories) +{ +} + +FileSearchPath::~FileSearchPath() +{ +} + +FileSearchPath& FileSearchPath::operator= (const String& path) +{ + init (path); + return *this; +} + +void FileSearchPath::init (const String& path) +{ + directories.clear(); + directories.addTokens (path, ";", "\""); + directories.trim(); + directories.removeEmptyStrings(); + + for (int i = directories.size(); --i >= 0;) + directories.set (i, directories[i].unquoted()); +} + +int FileSearchPath::getNumPaths() const +{ + return directories.size(); +} + +File FileSearchPath::operator[] (const int index) const +{ + return File (directories [index]); +} + +String FileSearchPath::toString() const +{ + StringArray directories2 (directories); + for (int i = directories2.size(); --i >= 0;) + if (directories2[i].containsChar (';')) + directories2.set (i, directories2[i].quoted()); + + return directories2.joinIntoString (";"); +} + +void FileSearchPath::add (const File& dir, const int insertIndex) +{ + directories.insert (insertIndex, dir.getFullPathName()); +} + +void FileSearchPath::addIfNotAlreadyThere (const File& dir) +{ + for (int i = 0; i < directories.size(); ++i) + if (File (directories[i]) == dir) + return; + + add (dir); +} + +void FileSearchPath::remove (const int index) +{ + directories.remove (index); +} + +void FileSearchPath::addPath (const FileSearchPath& other) +{ + for (int i = 0; i < other.getNumPaths(); ++i) + addIfNotAlreadyThere (other[i]); +} + +void FileSearchPath::removeRedundantPaths() +{ + for (int i = directories.size(); --i >= 0;) + { + const File d1 (directories[i]); + + for (int j = directories.size(); --j >= 0;) + { + const File d2 (directories[j]); + + if ((i != j) && (d1.isAChildOf (d2) || d1 == d2)) + { + directories.remove (i); + break; + } + } + } +} + +void FileSearchPath::removeNonExistentPaths() +{ + for (int i = directories.size(); --i >= 0;) + if (! File (directories[i]).isDirectory()) + directories.remove (i); +} + +int FileSearchPath::findChildFiles (Array& results, + const int whatToLookFor, + const bool searchRecursively, + const String& wildCardPattern) const +{ + int total = 0; + + for (int i = 0; i < directories.size(); ++i) + total += operator[] (i).findChildFiles (results, + whatToLookFor, + searchRecursively, + wildCardPattern); + + return total; +} + +bool FileSearchPath::isFileInPath (const File& fileToCheck, + const bool checkRecursively) const +{ + for (int i = directories.size(); --i >= 0;) + { + const File d (directories[i]); + + if (checkRecursively) + { + if (fileToCheck.isAChildOf (d)) + return true; + } + else + { + if (fileToCheck.getParentDirectory() == d) + return true; + } + } + + return false; +} diff --git a/modules/beast_core/files/beast_FileSearchPath.h b/modules/beast_core/files/beast_FileSearchPath.h new file mode 100644 index 0000000000..334b0a94de --- /dev/null +++ b/modules/beast_core/files/beast_FileSearchPath.h @@ -0,0 +1,164 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_FILESEARCHPATH_BEASTHEADER +#define BEAST_FILESEARCHPATH_BEASTHEADER + +#include "beast_File.h" +#include "../text/beast_StringArray.h" + + +//============================================================================== +/** + Encapsulates a set of folders that make up a search path. + + @see File +*/ +class BEAST_API FileSearchPath +{ +public: + //============================================================================== + /** Creates an empty search path. */ + FileSearchPath(); + + /** Creates a search path from a string of pathnames. + + The path can be semicolon- or comma-separated, e.g. + "/foo/bar;/foo/moose;/fish/moose" + + The separate folders are tokenised and added to the search path. + */ + FileSearchPath (const String& path); + + /** Creates a copy of another search path. */ + FileSearchPath (const FileSearchPath& other); + + /** Destructor. */ + ~FileSearchPath(); + + /** Uses a string containing a list of pathnames to re-initialise this list. + + This search path is cleared and the semicolon- or comma-separated folders + in this string are added instead. e.g. "/foo/bar;/foo/moose;/fish/moose" + */ + FileSearchPath& operator= (const String& path); + + //============================================================================== + /** Returns the number of folders in this search path. + + @see operator[] + */ + int getNumPaths() const; + + /** Returns one of the folders in this search path. + + The file returned isn't guaranteed to actually be a valid directory. + + @see getNumPaths + */ + File operator[] (int index) const; + + /** Returns the search path as a semicolon-separated list of directories. */ + String toString() const; + + //============================================================================== + /** Adds a new directory to the search path. + + The new directory is added to the end of the list if the insertIndex parameter is + less than zero, otherwise it is inserted at the given index. + */ + void add (const File& directoryToAdd, + int insertIndex = -1); + + /** Adds a new directory to the search path if it's not already in there. */ + void addIfNotAlreadyThere (const File& directoryToAdd); + + /** Removes a directory from the search path. */ + void remove (int indexToRemove); + + /** Merges another search path into this one. + + This will remove any duplicate directories. + */ + void addPath (const FileSearchPath& other); + + /** Removes any directories that are actually subdirectories of one of the other directories in the search path. + + If the search is intended to be recursive, there's no point having nested folders in the search + path, because they'll just get searched twice and you'll get duplicate results. + + e.g. if the path is "c:\abc\de;c:\abc", this method will simplify it to "c:\abc" + */ + void removeRedundantPaths(); + + /** Removes any directories that don't actually exist. */ + void removeNonExistentPaths(); + + //============================================================================== + /** Searches the path for a wildcard. + + This will search all the directories in the search path in order, adding any + matching files to the results array. + + @param results an array to append the results to + @param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying whether to + return files, directories, or both. + @param searchRecursively whether to recursively search the subdirectories too + @param wildCardPattern a pattern to match against the filenames + @returns the number of files added to the array + @see File::findChildFiles + */ + int findChildFiles (Array& results, + int whatToLookFor, + bool searchRecursively, + const String& wildCardPattern = "*") const; + + //============================================================================== + /** Finds out whether a file is inside one of the path's directories. + + This will return true if the specified file is a child of one of the + directories specified by this path. Note that this doesn't actually do any + searching or check that the files exist - it just looks at the pathnames + to work out whether the file would be inside a directory. + + @param fileToCheck the file to look for + @param checkRecursively if true, then this will return true if the file is inside a + subfolder of one of the path's directories (at any depth). If false + it will only return true if the file is actually a direct child + of one of the directories. + @see File::isAChildOf + + */ + bool isFileInPath (const File& fileToCheck, + bool checkRecursively) const; + +private: + //============================================================================== + StringArray directories; + + void init (const String& path); + + BEAST_LEAK_DETECTOR (FileSearchPath) +}; + +#endif // BEAST_FILESEARCHPATH_BEASTHEADER diff --git a/modules/beast_core/files/beast_MemoryMappedFile.h b/modules/beast_core/files/beast_MemoryMappedFile.h new file mode 100644 index 0000000000..0c20619b8a --- /dev/null +++ b/modules/beast_core/files/beast_MemoryMappedFile.h @@ -0,0 +1,111 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MEMORYMAPPEDFILE_BEASTHEADER +#define BEAST_MEMORYMAPPEDFILE_BEASTHEADER + +#include "beast_File.h" + +//============================================================================== +/** + Maps a file into virtual memory for easy reading and/or writing. +*/ +class BEAST_API MemoryMappedFile +{ +public: + /** The read/write flags used when opening a memory mapped file. */ + enum AccessMode + { + readOnly, /**< Indicates that the memory can only be read. */ + readWrite /**< Indicates that the memory can be read and written to - changes that are + made will be flushed back to disk at the whim of the OS. */ + }; + + /** Opens a file and maps it to an area of virtual memory. + + The file should already exist, and should already be the size that you want to work with + when you call this. If the file is resized after being opened, the behaviour is undefined. + + If the file exists and the operation succeeds, the getData() and getSize() methods will + return the location and size of the data that can be read or written. Note that the entire + file is not read into memory immediately - the OS simply creates a virtual mapping, which + will lazily pull the data into memory when blocks are accessed. + + If the file can't be opened for some reason, the getData() method will return a null pointer. + */ + MemoryMappedFile (const File& file, AccessMode mode); + + /** Opens a section of a file and maps it to an area of virtual memory. + + The file should already exist, and should already be the size that you want to work with + when you call this. If the file is resized after being opened, the behaviour is undefined. + + If the file exists and the operation succeeds, the getData() and getSize() methods will + return the location and size of the data that can be read or written. Note that the entire + file is not read into memory immediately - the OS simply creates a virtual mapping, which + will lazily pull the data into memory when blocks are accessed. + + If the file can't be opened for some reason, the getData() method will return a null pointer. + + NOTE: the start of the actual range used may be rounded-down to a multiple of the OS's page-size, + so do not assume that the mapped memory will begin at exactly the position you requested - always + use getRange() to check the actual range that is being used. + */ + MemoryMappedFile (const File& file, + const Range& fileRange, + AccessMode mode); + + /** Destructor. */ + ~MemoryMappedFile(); + + /** Returns the address at which this file has been mapped, or a null pointer if + the file couldn't be successfully mapped. + */ + void* getData() const noexcept { return address; } + + /** Returns the number of bytes of data that are available for reading or writing. + This will normally be the size of the file. + */ + size_t getSize() const noexcept { return (size_t) range.getLength(); } + + /** Returns the section of the file at which the mapped memory represents. */ + Range getRange() const noexcept { return range; } + +private: + //============================================================================== + void* address; + Range range; + + #if BEAST_WINDOWS + void* fileHandle; + #else + int fileHandle; + #endif + + void openInternal (const File&, AccessMode); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedFile) +}; + + +#endif // BEAST_MEMORYMAPPEDFILE_BEASTHEADER diff --git a/modules/beast_core/files/beast_TemporaryFile.cpp b/modules/beast_core/files/beast_TemporaryFile.cpp new file mode 100644 index 0000000000..d38100875d --- /dev/null +++ b/modules/beast_core/files/beast_TemporaryFile.cpp @@ -0,0 +1,112 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +static File createTempFile (const File& parentDirectory, String name, + const String& suffix, const int optionFlags) +{ + if ((optionFlags & TemporaryFile::useHiddenFile) != 0) + name = "." + name; + + return parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & TemporaryFile::putNumbersInBrackets) != 0); +} + +TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) + : temporaryFile (createTempFile (File::getSpecialLocation (File::tempDirectory), + "temp_" + String::toHexString (Random::getSystemRandom().nextInt()), + suffix, optionFlags)) +{ +} + +TemporaryFile::TemporaryFile (const File& target, const int optionFlags) + : temporaryFile (createTempFile (target.getParentDirectory(), + target.getFileNameWithoutExtension() + + "_temp" + String::toHexString (Random::getSystemRandom().nextInt()), + target.getFileExtension(), optionFlags)), + targetFile (target) +{ + // If you use this constructor, you need to give it a valid target file! + bassert (targetFile != File::nonexistent); +} + +TemporaryFile::TemporaryFile (const File& target, const File& temporary) + : temporaryFile (temporary), targetFile (target) +{ +} + +TemporaryFile::~TemporaryFile() +{ + if (! deleteTemporaryFile()) + { + /* Failed to delete our temporary file! The most likely reason for this would be + that you've not closed an output stream that was being used to write to file. + + If you find that something beyond your control is changing permissions on + your temporary files and preventing them from being deleted, you may want to + call TemporaryFile::deleteTemporaryFile() to detect those error cases and + handle them appropriately. + */ + jassertfalse; + } +} + +//============================================================================== +bool TemporaryFile::overwriteTargetFileWithTemporary() const +{ + // This method only works if you created this object with the constructor + // that takes a target file! + bassert (targetFile != File::nonexistent); + + if (temporaryFile.exists()) + { + // Have a few attempts at overwriting the file before giving up.. + for (int i = 5; --i >= 0;) + { + if (temporaryFile.moveFileTo (targetFile)) + return true; + + Thread::sleep (100); + } + } + else + { + // There's no temporary file to use. If your write failed, you should + // probably check, and not bother calling this method. + jassertfalse; + } + + return false; +} + +bool TemporaryFile::deleteTemporaryFile() const +{ + // Have a few attempts at deleting the file before giving up.. + for (int i = 5; --i >= 0;) + { + if (temporaryFile.deleteFile()) + return true; + + Thread::sleep (50); + } + + return false; +} diff --git a/modules/beast_core/files/beast_TemporaryFile.h b/modules/beast_core/files/beast_TemporaryFile.h new file mode 100644 index 0000000000..bdc9a67284 --- /dev/null +++ b/modules/beast_core/files/beast_TemporaryFile.h @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_TEMPORARYFILE_BEASTHEADER +#define BEAST_TEMPORARYFILE_BEASTHEADER + +#include "beast_File.h" + + +//============================================================================== +/** + Manages a temporary file, which will be deleted when this object is deleted. + + This object is intended to be used as a stack based object, using its scope + to make sure the temporary file isn't left lying around. + + For example: + + @code + { + File myTargetFile ("~/myfile.txt"); + + // this will choose a file called something like "~/myfile_temp239348.txt" + // which definitely doesn't exist at the time the constructor is called. + TemporaryFile temp (myTargetFile); + + // create a stream to the temporary file, and write some data to it... + ScopedPointer out (temp.getFile().createOutputStream()); + + if (out != nullptr) + { + out->write ( ...etc ) + out = nullptr; // (deletes the stream) + + // ..now we've finished writing, this will rename the temp file to + // make it replace the target file we specified above. + bool succeeded = temp.overwriteTargetFileWithTemporary(); + } + + // ..and even if something went wrong and our overwrite failed, + // as the TemporaryFile object goes out of scope here, it'll make sure + // that the temp file gets deleted. + } + @endcode + + @see File, FileOutputStream +*/ +class BEAST_API TemporaryFile +{ +public: + //============================================================================== + enum OptionFlags + { + useHiddenFile = 1, /**< Indicates that the temporary file should be hidden - + i.e. its name should start with a dot. */ + putNumbersInBrackets = 2 /**< Indicates that when numbers are appended to make sure + the file is unique, they should go in brackets rather + than just being appended (see File::getNonexistentSibling() )*/ + }; + + //============================================================================== + /** Creates a randomly-named temporary file in the default temp directory. + + @param suffix a file suffix to use for the file + @param optionFlags a combination of the values listed in the OptionFlags enum + The file will not be created until you write to it. And remember that when + this object is deleted, the file will also be deleted! + */ + TemporaryFile (const String& suffix = String::empty, + int optionFlags = 0); + + /** Creates a temporary file in the same directory as a specified file. + + This is useful if you have a file that you want to overwrite, but don't + want to harm the original file if the write operation fails. You can + use this to create a temporary file next to the target file, then + write to the temporary file, and finally use overwriteTargetFileWithTemporary() + to replace the target file with the one you've just written. + + This class won't create any files until you actually write to them. And remember + that when this object is deleted, the temporary file will also be deleted! + + @param targetFile the file that you intend to overwrite - the temporary + file will be created in the same directory as this + @param optionFlags a combination of the values listed in the OptionFlags enum + */ + TemporaryFile (const File& targetFile, + int optionFlags = 0); + + /** Creates a temporary file using an explicit filename. + The other constructors are a better choice than this one, unless for some reason + you need to explicitly specify the temporary file you want to use. + + @param targetFile the file that you intend to overwrite + @param temporaryFile the temporary file to be used + */ + TemporaryFile (const File& targetFile, + const File& temporaryFile); + + /** Destructor. + + When this object is deleted it will make sure that its temporary file is + also deleted! If the operation fails, it'll throw an assertion in debug + mode. + */ + ~TemporaryFile(); + + //============================================================================== + /** Returns the temporary file. */ + const File& getFile() const noexcept { return temporaryFile; } + + /** Returns the target file that was specified in the constructor. */ + const File& getTargetFile() const noexcept { return targetFile; } + + /** Tries to move the temporary file to overwrite the target file that was + specified in the constructor. + + If you used the constructor that specified a target file, this will attempt + to replace that file with the temporary one. + + Before calling this, make sure: + - that you've actually written to the temporary file + - that you've closed any open streams that you were using to write to it + - and that you don't have any streams open to the target file, which would + prevent it being overwritten + + If the file move succeeds, this returns false, and the temporary file will + have disappeared. If it fails, the temporary file will probably still exist, + but will be deleted when this object is destroyed. + */ + bool overwriteTargetFileWithTemporary() const; + + /** Attempts to delete the temporary file, if it exists. + @returns true if the file is successfully deleted (or if it didn't exist). + */ + bool deleteTemporaryFile() const; + + +private: + //============================================================================== + const File temporaryFile, targetFile; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryFile) +}; + +#endif // BEAST_TEMPORARYFILE_BEASTHEADER diff --git a/modules/beast_core/json/beast_JSON.cpp b/modules/beast_core/json/beast_JSON.cpp new file mode 100644 index 0000000000..a3f7548fe5 --- /dev/null +++ b/modules/beast_core/json/beast_JSON.cpp @@ -0,0 +1,645 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +class JSONParser +{ +public: + static Result parseObjectOrArray (String::CharPointerType t, var& result) + { + t = t.findEndOfWhitespace(); + + switch (t.getAndAdvance()) + { + case 0: result = var::null; return Result::ok(); + case '{': return parseObject (t, result); + case '[': return parseArray (t, result); + } + + return createFail ("Expected '{' or '['", &t); + } + +private: + static Result parseAny (String::CharPointerType& t, var& result) + { + t = t.findEndOfWhitespace(); + String::CharPointerType t2 (t); + + switch (t2.getAndAdvance()) + { + case '{': t = t2; return parseObject (t, result); + case '[': t = t2; return parseArray (t, result); + case '"': t = t2; return parseString (t, result); + + case '-': + t2 = t2.findEndOfWhitespace(); + if (! CharacterFunctions::isDigit (*t2)) + break; + + t = t2; + return parseNumber (t, result, true); + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parseNumber (t, result, false); + + case 't': // "true" + if (t2.getAndAdvance() == 'r' && t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'e') + { + t = t2; + result = var (true); + return Result::ok(); + } + break; + + case 'f': // "false" + if (t2.getAndAdvance() == 'a' && t2.getAndAdvance() == 'l' + && t2.getAndAdvance() == 's' && t2.getAndAdvance() == 'e') + { + t = t2; + result = var (false); + return Result::ok(); + } + break; + + case 'n': // "null" + if (t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'l' && t2.getAndAdvance() == 'l') + { + t = t2; + result = var::null; + return Result::ok(); + } + break; + + default: + break; + } + + return createFail ("Syntax error", &t); + } + + static Result createFail (const char* const message, const String::CharPointerType* location = nullptr) + { + String m (message); + if (location != nullptr) + m << ": \"" << String (*location, 20) << '"'; + + return Result::fail (m); + } + + static Result parseNumber (String::CharPointerType& t, var& result, const bool isNegative) + { + String::CharPointerType oldT (t); + + int64 intValue = t.getAndAdvance() - '0'; + bassert (intValue >= 0 && intValue < 10); + + for (;;) + { + String::CharPointerType previousChar (t); + const beast_wchar c = t.getAndAdvance(); + const int digit = ((int) c) - '0'; + + if (isPositiveAndBelow (digit, 10)) + { + intValue = intValue * 10 + digit; + continue; + } + + if (c == 'e' || c == 'E' || c == '.') + { + t = oldT; + const double asDouble = CharacterFunctions::readDoubleValue (t); + result = isNegative ? -asDouble : asDouble; + return Result::ok(); + } + + if (CharacterFunctions::isWhitespace (c) + || c == ',' || c == '}' || c == ']' || c == 0) + { + t = previousChar; + break; + } + + return createFail ("Syntax error in number", &oldT); + } + + const int64 correctedValue = isNegative ? -intValue : intValue; + + if ((intValue >> 31) != 0) + result = correctedValue; + else + result = (int) correctedValue; + + return Result::ok(); + } + + static Result parseObject (String::CharPointerType& t, var& result) + { + DynamicObject* const resultObject = new DynamicObject(); + result = resultObject; + NamedValueSet& resultProperties = resultObject->getProperties(); + + for (;;) + { + t = t.findEndOfWhitespace(); + + String::CharPointerType oldT (t); + const beast_wchar c = t.getAndAdvance(); + + if (c == '}') + break; + + if (c == 0) + return createFail ("Unexpected end-of-input in object declaration"); + + if (c == '"') + { + var propertyNameVar; + Result r (parseString (t, propertyNameVar)); + + if (r.failed()) + return r; + + const String propertyName (propertyNameVar.toString()); + + if (propertyName.isNotEmpty()) + { + t = t.findEndOfWhitespace(); + oldT = t; + + const beast_wchar c2 = t.getAndAdvance(); + if (c2 != ':') + return createFail ("Expected ':', but found", &oldT); + + resultProperties.set (propertyName, var::null); + var* propertyValue = resultProperties.getVarPointer (propertyName); + + Result r2 (parseAny (t, *propertyValue)); + + if (r2.failed()) + return r2; + + t = t.findEndOfWhitespace(); + oldT = t; + + const beast_wchar nextChar = t.getAndAdvance(); + + if (nextChar == ',') + continue; + + if (nextChar == '}') + break; + } + } + + return createFail ("Expected object member declaration, but found", &oldT); + } + + return Result::ok(); + } + + static Result parseArray (String::CharPointerType& t, var& result) + { + result = var (Array()); + Array* const destArray = result.getArray(); + + for (;;) + { + t = t.findEndOfWhitespace(); + + String::CharPointerType oldT (t); + const beast_wchar c = t.getAndAdvance(); + + if (c == ']') + break; + + if (c == 0) + return createFail ("Unexpected end-of-input in array declaration"); + + t = oldT; + destArray->add (var::null); + Result r (parseAny (t, destArray->getReference (destArray->size() - 1))); + + if (r.failed()) + return r; + + t = t.findEndOfWhitespace(); + oldT = t; + + const beast_wchar nextChar = t.getAndAdvance(); + + if (nextChar == ',') + continue; + + if (nextChar == ']') + break; + + return createFail ("Expected object array item, but found", &oldT); + } + + return Result::ok(); + } + + static Result parseString (String::CharPointerType& t, var& result) + { + MemoryOutputStream buffer (256); + + for (;;) + { + beast_wchar c = t.getAndAdvance(); + + if (c == '"') + break; + + if (c == '\\') + { + c = t.getAndAdvance(); + + switch (c) + { + case '"': + case '\\': + case '/': break; + + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + + case 'u': + { + c = 0; + + for (int i = 4; --i >= 0;) + { + const int digitValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); + if (digitValue < 0) + return createFail ("Syntax error in unicode escape sequence"); + + c = (beast_wchar) ((c << 4) + digitValue); + } + + break; + } + } + } + + if (c == 0) + return createFail ("Unexpected end-of-input in string constant"); + + buffer.appendUTF8Char (c); + } + + result = buffer.toString(); + return Result::ok(); + } +}; + +//============================================================================== +class JSONFormatter +{ +public: + static void write (OutputStream& out, const var& v, + const int indentLevel, const bool allOnOneLine) + { + if (v.isString()) + { + writeString (out, v.toString().getCharPointer()); + } + else if (v.isVoid()) + { + out << "null"; + } + else if (v.isBool()) + { + out << (static_cast (v) ? "true" : "false"); + } + else if (v.isArray()) + { + writeArray (out, *v.getArray(), indentLevel, allOnOneLine); + } + else if (v.isObject()) + { + if (DynamicObject* const object = v.getDynamicObject()) + writeObject (out, *object, indentLevel, allOnOneLine); + else + jassertfalse; // Only DynamicObjects can be converted to JSON! + } + else + { + // Can't convert these other types of object to JSON! + bassert (! (v.isMethod() || v.isBinaryData())); + + out << v.toString(); + } + } + +private: + enum { indentSize = 2 }; + + static void writeEscapedChar (OutputStream& out, const unsigned short value) + { + out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4); + } + + static void writeString (OutputStream& out, String::CharPointerType t) + { + out << '"'; + + for (;;) + { + const beast_wchar c (t.getAndAdvance()); + + switch (c) + { + case 0: out << '"'; return; + + case '\"': out << "\\\""; break; + case '\\': out << "\\\\"; break; + case '\b': out << "\\b"; break; + case '\f': out << "\\f"; break; + case '\t': out << "\\t"; break; + case '\r': out << "\\r"; break; + case '\n': out << "\\n"; break; + + default: + if (c >= 32 && c < 127) + { + out << (char) c; + } + else + { + if (CharPointer_UTF16::getBytesRequiredFor (c) > 2) + { + CharPointer_UTF16::CharType chars[2]; + CharPointer_UTF16 utf16 (chars); + utf16.write (c); + + for (int i = 0; i < 2; ++i) + writeEscapedChar (out, (unsigned short) chars[i]); + } + else + { + writeEscapedChar (out, (unsigned short) c); + } + } + + break; + } + } + } + + static void writeSpaces (OutputStream& out, int numSpaces) + { + out.writeRepeatedByte (' ', (size_t) numSpaces); + } + + static void writeArray (OutputStream& out, const Array& array, + const int indentLevel, const bool allOnOneLine) + { + out << '['; + if (! allOnOneLine) + out << newLine; + + for (int i = 0; i < array.size(); ++i) + { + if (! allOnOneLine) + writeSpaces (out, indentLevel + indentSize); + + write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine); + + if (i < array.size() - 1) + { + if (allOnOneLine) + out << ", "; + else + out << ',' << newLine; + } + else if (! allOnOneLine) + out << newLine; + } + + if (! allOnOneLine) + writeSpaces (out, indentLevel); + + out << ']'; + } + + static void writeObject (OutputStream& out, DynamicObject& object, + const int indentLevel, const bool allOnOneLine) + { + NamedValueSet& props = object.getProperties(); + + out << '{'; + if (! allOnOneLine) + out << newLine; + + LinkedListPointer* i = &(props.values); + + for (;;) + { + NamedValueSet::NamedValue* const v = i->get(); + + if (v == nullptr) + break; + + if (! allOnOneLine) + writeSpaces (out, indentLevel + indentSize); + + writeString (out, v->name); + out << ": "; + write (out, v->value, indentLevel + indentSize, allOnOneLine); + + if (v->nextListItem.get() != nullptr) + { + if (allOnOneLine) + out << ", "; + else + out << ',' << newLine; + } + else if (! allOnOneLine) + out << newLine; + + i = &(v->nextListItem); + } + + if (! allOnOneLine) + writeSpaces (out, indentLevel); + + out << '}'; + } +}; + +//============================================================================== +var JSON::parse (const String& text) +{ + var result; + + if (! JSONParser::parseObjectOrArray (text.getCharPointer(), result)) + result = var::null; + + return result; +} + +var JSON::parse (InputStream& input) +{ + return parse (input.readEntireStreamAsString()); +} + +var JSON::parse (const File& file) +{ + return parse (file.loadFileAsString()); +} + +Result JSON::parse (const String& text, var& result) +{ + return JSONParser::parseObjectOrArray (text.getCharPointer(), result); +} + +String JSON::toString (const var& data, const bool allOnOneLine) +{ + MemoryOutputStream mo (1024); + JSONFormatter::write (mo, data, 0, allOnOneLine); + return mo.toString(); +} + +void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine) +{ + JSONFormatter::write (output, data, 0, allOnOneLine); +} + +//============================================================================== +//============================================================================== +#if BEAST_UNIT_TESTS + +class JSONTests : public UnitTest +{ +public: + JSONTests() : UnitTest ("JSON") {} + + static String createRandomWideCharString (Random& r) + { + beast_wchar buffer[40] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (beast_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (beast_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } + + static String createRandomIdentifier (Random& r) + { + char buffer[30] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:"; + buffer[i] = chars [r.nextInt (sizeof (chars) - 1)]; + } + + return CharPointer_ASCII (buffer); + } + + static var createRandomVar (Random& r, int depth) + { + switch (r.nextInt (depth > 3 ? 6 : 8)) + { + case 0: return var::null; + case 1: return r.nextInt(); + case 2: return r.nextInt64(); + case 3: return r.nextBool(); + case 4: return r.nextDouble(); + case 5: return createRandomWideCharString (r); + + case 6: + { + var v (createRandomVar (r, depth + 1)); + + for (int i = 1 + r.nextInt (30); --i >= 0;) + v.append (createRandomVar (r, depth + 1)); + + return v; + } + + case 7: + { + DynamicObject* o = new DynamicObject(); + + for (int i = r.nextInt (30); --i >= 0;) + o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1)); + + return o; + } + + default: + return var::null; + } + } + + void runTest() + { + beginTest ("JSON"); + Random r; + r.setSeedRandomly(); + + expect (JSON::parse (String::empty) == var::null); + expect (JSON::parse ("{}").isObject()); + expect (JSON::parse ("[]").isArray()); + expect (JSON::parse ("[ 1234 ]")[0].isInt()); + expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64()); + expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble()); + expect (JSON::parse ("[ -1234]")[0].isInt()); + expect (JSON::parse ("[-12345678901234]")[0].isInt64()); + expect (JSON::parse ("[-1.123e3]")[0].isDouble()); + + for (int i = 100; --i >= 0;) + { + var v; + + if (i > 0) + v = createRandomVar (r, 0); + + const bool oneLine = r.nextBool(); + String asString (JSON::toString (v, oneLine)); + var parsed = JSON::parse ("[" + asString + "]")[0]; + String parsedString (JSON::toString (parsed, oneLine)); + expect (asString.isNotEmpty() && parsedString == asString); + } + } +}; + +static JSONTests JSONUnitTests; + +#endif diff --git a/modules/beast_core/json/beast_JSON.h b/modules/beast_core/json/beast_JSON.h new file mode 100644 index 0000000000..f1799d434a --- /dev/null +++ b/modules/beast_core/json/beast_JSON.h @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_JSON_BEASTHEADER +#define BEAST_JSON_BEASTHEADER + +#include "../misc/beast_Result.h" +#include "../containers/beast_Variant.h" +class InputStream; +class OutputStream; +class File; + + +//============================================================================== +/** + Contains static methods for converting JSON-formatted text to and from var objects. + + The var class is structurally compatible with JSON-formatted data, so these + functions allow you to parse JSON into a var object, and to convert a var + object to JSON-formatted text. + + @see var +*/ +class BEAST_API JSON +{ +public: + //============================================================================== + /** Parses a string of JSON-formatted text, and returns a result code containing + any parse errors. + + This will return the parsed structure in the parsedResult parameter, and will + return a Result object to indicate whether parsing was successful, and if not, + it will contain an error message. + + If you're not interested in the error message, you can use one of the other + shortcut parse methods, which simply return a var::null if the parsing fails. + */ + static Result parse (const String& text, var& parsedResult); + + /** Attempts to parse some JSON-formatted text, and returns the result as a var object. + + If the parsing fails, this simply returns var::null - if you need to find out more + detail about the parse error, use the alternative parse() method which returns a Result. + */ + static var parse (const String& text); + + /** Attempts to parse some JSON-formatted text from a file, and returns the result + as a var object. + + Note that this is just a short-cut for reading the entire file into a string and + parsing the result. + + If the parsing fails, this simply returns var::null - if you need to find out more + detail about the parse error, use the alternative parse() method which returns a Result. + */ + static var parse (const File& file); + + /** Attempts to parse some JSON-formatted text from a stream, and returns the result + as a var object. + + Note that this is just a short-cut for reading the entire stream into a string and + parsing the result. + + If the parsing fails, this simply returns var::null - if you need to find out more + detail about the parse error, use the alternative parse() method which returns a Result. + */ + static var parse (InputStream& input); + + //============================================================================== + /** Returns a string which contains a JSON-formatted representation of the var object. + If allOnOneLine is true, the result will be compacted into a single line of text + with no carriage-returns. If false, it will be laid-out in a more human-readable format. + @see writeToStream + */ + static String toString (const var& objectToFormat, + bool allOnOneLine = false); + + /** Writes a JSON-formatted representation of the var object to the given stream. + If allOnOneLine is true, the result will be compacted into a single line of text + with no carriage-returns. If false, it will be laid-out in a more human-readable format. + @see toString + */ + static void writeToStream (OutputStream& output, + const var& objectToFormat, + bool allOnOneLine = false); + +private: + //============================================================================== + JSON(); // This class can't be instantiated - just use its static methods. +}; + + +#endif // BEAST_JSON_BEASTHEADER diff --git a/modules/beast_core/logging/beast_FileLogger.cpp b/modules/beast_core/logging/beast_FileLogger.cpp new file mode 100644 index 0000000000..fa19b3afe8 --- /dev/null +++ b/modules/beast_core/logging/beast_FileLogger.cpp @@ -0,0 +1,129 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +FileLogger::FileLogger (const File& file, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes) + : logFile (file) +{ + if (maxInitialFileSizeBytes >= 0) + trimFileSize (maxInitialFileSizeBytes); + + if (! file.exists()) + file.create(); // (to create the parent directories) + + String welcome; + welcome << newLine + << "**********************************************************" << newLine + << welcomeMessage << newLine + << "Log started: " << Time::getCurrentTime().toString (true, true) << newLine; + + FileLogger::logMessage (welcome); +} + +FileLogger::~FileLogger() {} + +//============================================================================== +void FileLogger::logMessage (const String& message) +{ + const ScopedLock sl (logLock); + DBG (message); + FileOutputStream out (logFile, 256); + out << message << newLine; +} + +void FileLogger::trimFileSize (int64 maxFileSizeBytes) const +{ + if (maxFileSizeBytes <= 0) + { + logFile.deleteFile(); + } + else + { + const int64 fileSize = logFile.getSize(); + + if (fileSize > maxFileSizeBytes) + { + TemporaryFile tempFile (logFile); + + { + FileOutputStream out (tempFile.getFile()); + FileInputStream in (logFile); + + if (! (out.openedOk() && in.openedOk())) + return; + + in.setPosition (fileSize - maxFileSizeBytes); + + for (;;) + { + const char c = in.readByte(); + if (c == 0) + return; + + if (c == '\n' || c == '\r') + { + out << c; + break; + } + } + + out.writeFromInputStream (in, -1); + } + + tempFile.overwriteTargetFileWithTemporary(); + } + } +} + +//============================================================================== +File FileLogger::getSystemLogFileFolder() +{ + #if BEAST_MAC + return File ("~/Library/Logs"); + #else + return File::getSpecialLocation (File::userApplicationDataDirectory); + #endif +} + +FileLogger* FileLogger::createDefaultAppLogger (const String& logFileSubDirectoryName, + const String& logFileName, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes) +{ + return new FileLogger (getSystemLogFileFolder().getChildFile (logFileSubDirectoryName) + .getChildFile (logFileName), + welcomeMessage, maxInitialFileSizeBytes); +} + +FileLogger* FileLogger::createDateStampedLogger (const String& logFileSubDirectoryName, + const String& logFileNameRoot, + const String& logFileNameSuffix, + const String& welcomeMessage) +{ + return new FileLogger (getSystemLogFileFolder().getChildFile (logFileSubDirectoryName) + .getChildFile (logFileNameRoot + Time::getCurrentTime().formatted ("%Y-%m-%d_%H-%M-%S")) + .withFileExtension (logFileNameSuffix) + .getNonexistentSibling(), + welcomeMessage, 0); +} diff --git a/modules/beast_core/logging/beast_FileLogger.h b/modules/beast_core/logging/beast_FileLogger.h new file mode 100644 index 0000000000..be3ae39963 --- /dev/null +++ b/modules/beast_core/logging/beast_FileLogger.h @@ -0,0 +1,134 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_FILELOGGER_BEASTHEADER +#define BEAST_FILELOGGER_BEASTHEADER + +#include "beast_Logger.h" +#include "../files/beast_File.h" +#include "../memory/beast_ScopedPointer.h" + + +//============================================================================== +/** + A simple implementation of a Logger that writes to a file. + + @see Logger +*/ +class BEAST_API FileLogger : public Logger +{ +public: + //============================================================================== + /** Creates a FileLogger for a given file. + + @param fileToWriteTo the file that to use - new messages will be appended + to the file. If the file doesn't exist, it will be created, + along with any parent directories that are needed. + @param welcomeMessage when opened, the logger will write a header to the log, along + with the current date and time, and this welcome message + @param maxInitialFileSizeBytes if this is zero or greater, then if the file already exists + but is larger than this number of bytes, then the start of the + file will be truncated to keep the size down. This prevents a log + file getting ridiculously large over time. The file will be truncated + at a new-line boundary. If this value is less than zero, no size limit + will be imposed; if it's zero, the file will always be deleted. Note that + the size is only checked once when this object is created - any logging + that is done later will be appended without any checking + */ + FileLogger (const File& fileToWriteTo, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes = 128 * 1024); + + /** Destructor. */ + ~FileLogger(); + + //============================================================================== + /** Returns the file that this logger is writing to. */ + const File& getLogFile() const noexcept { return logFile; } + + //============================================================================== + /** Helper function to create a log file in the correct place for this platform. + + The method might return nullptr if the file can't be created for some reason. + + @param logFileSubDirectoryName the name of the subdirectory to create inside the logs folder (as + returned by getSystemLogFileFolder). It's best to use something + like the name of your application here. + @param logFileName the name of the file to create, e.g. "MyAppLog.txt". + @param welcomeMessage a message that will be written to the log when it's opened. + @param maxInitialFileSizeBytes (see the FileLogger constructor for more info on this) + */ + static FileLogger* createDefaultAppLogger (const String& logFileSubDirectoryName, + const String& logFileName, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes = 128 * 1024); + + /** Helper function to create a log file in the correct place for this platform. + + The filename used is based on the root and suffix strings provided, along with a + time and date string, meaning that a new, empty log file will be always be created + rather than appending to an exising one. + + The method might return nullptr if the file can't be created for some reason. + + @param logFileSubDirectoryName the name of the subdirectory to create inside the logs folder (as + returned by getSystemLogFileFolder). It's best to use something + like the name of your application here. + @param logFileNameRoot the start of the filename to use, e.g. "MyAppLog_". This will have + a timestamp and the logFileNameSuffix appended to it + @param logFileNameSuffix the file suffix to use, e.g. ".txt" + @param welcomeMessage a message that will be written to the log when it's opened. + */ + static FileLogger* createDateStampedLogger (const String& logFileSubDirectoryName, + const String& logFileNameRoot, + const String& logFileNameSuffix, + const String& welcomeMessage); + + //============================================================================== + /** Returns an OS-specific folder where log-files should be stored. + + On Windows this will return a logger with a path such as: + c:\\Documents and Settings\\username\\Application Data\\[logFileSubDirectoryName]\\[logFileName] + + On the Mac it'll create something like: + ~/Library/Logs/[logFileSubDirectoryName]/[logFileName] + + @see createDefaultAppLogger + */ + static File getSystemLogFileFolder(); + + // (implementation of the Logger virtual method) + void logMessage (const String&); + +private: + //============================================================================== + File logFile; + CriticalSection logLock; + + void trimFileSize (int64 maxFileSizeBytes) const; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileLogger) +}; + + +#endif // BEAST_FILELOGGER_BEASTHEADER diff --git a/modules/beast_core/logging/beast_Logger.cpp b/modules/beast_core/logging/beast_Logger.cpp new file mode 100644 index 0000000000..d8ac98e66d --- /dev/null +++ b/modules/beast_core/logging/beast_Logger.cpp @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +Logger::Logger() {} + +Logger::~Logger() +{ + // You're deleting this logger while it's still being used! + // Always call Logger::setCurrentLogger (nullptr) before deleting the active logger. + bassert (currentLogger != this); +} + +Logger* Logger::currentLogger = nullptr; + +void Logger::setCurrentLogger (Logger* const newLogger) noexcept { currentLogger = newLogger; } +Logger* Logger::getCurrentLogger() noexcept { return currentLogger; } + +void Logger::writeToLog (const String& message) +{ + if (currentLogger != nullptr) + currentLogger->logMessage (message); + else + outputDebugString (message); +} + +#if BEAST_LOG_ASSERTIONS || BEAST_DEBUG +void BEAST_API BEAST_CALLTYPE logAssertion (const char* const filename, const int lineNum) noexcept +{ + String m ("BEAST Assertion failure in "); + m << File::createFileWithoutCheckingPath (filename).getFileName() << ':' << lineNum; + + #if BEAST_LOG_ASSERTIONS + Logger::writeToLog (m); + #else + DBG (m); + #endif +} +#endif diff --git a/modules/beast_core/logging/beast_Logger.h b/modules/beast_core/logging/beast_Logger.h new file mode 100644 index 0000000000..203ea209b3 --- /dev/null +++ b/modules/beast_core/logging/beast_Logger.h @@ -0,0 +1,94 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_LOGGER_BEASTHEADER +#define BEAST_LOGGER_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** + Acts as an application-wide logging class. + + A subclass of Logger can be created and passed into the Logger::setCurrentLogger + method and this will then be used by all calls to writeToLog. + + The logger class also contains methods for writing messages to the debugger's + output stream. + + @see FileLogger +*/ +class BEAST_API Logger +{ +public: + //============================================================================== + /** Destructor. */ + virtual ~Logger(); + + //============================================================================== + /** Sets the current logging class to use. + + Note that the object passed in will not be owned or deleted by the logger, so + the caller must make sure that it is not deleted while still being used. + A null pointer can be passed-in to disable any logging. + */ + static void BEAST_CALLTYPE setCurrentLogger (Logger* newLogger) noexcept; + + /** Returns the current logger, or nullptr if none has been set. */ + static Logger* getCurrentLogger() noexcept; + + /** Writes a string to the current logger. + + This will pass the string to the logger's logMessage() method if a logger + has been set. + + @see logMessage + */ + static void BEAST_CALLTYPE writeToLog (const String& message); + + + //============================================================================== + /** Writes a message to the standard error stream. + + This can be called directly, or by using the DBG() macro in + beast_PlatformDefs.h (which will avoid calling the method in non-debug builds). + */ + static void BEAST_CALLTYPE outputDebugString (const String& text); + + +protected: + //============================================================================== + Logger(); + + /** This is overloaded by subclasses to implement custom logging behaviour. + @see setCurrentLogger + */ + virtual void logMessage (const String& message) = 0; + +private: + static Logger* currentLogger; +}; + + +#endif // BEAST_LOGGER_BEASTHEADER diff --git a/modules/beast_core/maths/beast_BigInteger.cpp b/modules/beast_core/maths/beast_BigInteger.cpp new file mode 100644 index 0000000000..711c4ee8fd --- /dev/null +++ b/modules/beast_core/maths/beast_BigInteger.cpp @@ -0,0 +1,1016 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +namespace +{ + inline size_t bitToIndex (const int bit) noexcept { return (size_t) (bit >> 5); } + inline uint32 bitToMask (const int bit) noexcept { return (uint32) 1 << (bit & 31); } +} + +//============================================================================== +BigInteger::BigInteger() + : numValues (4), + highestBit (-1), + negative (false) +{ + values.calloc (numValues + 1); +} + +BigInteger::BigInteger (const int32 value) + : numValues (4), + highestBit (31), + negative (value < 0) +{ + values.calloc (numValues + 1); + values[0] = (uint32) abs (value); + highestBit = getHighestBit(); +} + +BigInteger::BigInteger (const uint32 value) + : numValues (4), + highestBit (31), + negative (false) +{ + values.calloc (numValues + 1); + values[0] = value; + highestBit = getHighestBit(); +} + +BigInteger::BigInteger (int64 value) + : numValues (4), + highestBit (63), + negative (value < 0) +{ + values.calloc (numValues + 1); + + if (value < 0) + value = -value; + + values[0] = (uint32) value; + values[1] = (uint32) (value >> 32); + highestBit = getHighestBit(); +} + +BigInteger::BigInteger (const BigInteger& other) + : numValues ((size_t) bmax ((size_t) 4, bitToIndex (other.highestBit) + 1)), + highestBit (other.getHighestBit()), + negative (other.negative) +{ + values.malloc (numValues + 1); + memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +BigInteger::BigInteger (BigInteger&& other) noexcept + : values (static_cast &&> (other.values)), + numValues (other.numValues), + highestBit (other.highestBit), + negative (other.negative) +{ +} + +BigInteger& BigInteger::operator= (BigInteger&& other) noexcept +{ + values = static_cast &&> (other.values); + numValues = other.numValues; + highestBit = other.highestBit; + negative = other.negative; + return *this; +} +#endif + +BigInteger::~BigInteger() +{ +} + +void BigInteger::swapWith (BigInteger& other) noexcept +{ + values.swapWith (other.values); + std::swap (numValues, other.numValues); + std::swap (highestBit, other.highestBit); + std::swap (negative, other.negative); +} + +BigInteger& BigInteger::operator= (const BigInteger& other) +{ + if (this != &other) + { + highestBit = other.getHighestBit(); + bassert (other.numValues >= 4); + numValues = (size_t) bmax ((size_t) 4, bitToIndex (highestBit) + 1); + negative = other.negative; + values.malloc (numValues + 1); + memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); + } + + return *this; +} + +void BigInteger::ensureSize (const size_t numVals) +{ + if (numVals + 2 >= numValues) + { + size_t oldSize = numValues; + numValues = ((numVals + 2) * 3) / 2; + values.realloc (numValues + 1); + + while (oldSize < numValues) + values [oldSize++] = 0; + } +} + +//============================================================================== +bool BigInteger::operator[] (const int bit) const noexcept +{ + return bit <= highestBit && bit >= 0 + && ((values [bitToIndex (bit)] & bitToMask (bit)) != 0); +} + +int BigInteger::toInteger() const noexcept +{ + const int n = (int) (values[0] & 0x7fffffff); + return negative ? -n : n; +} + +BigInteger BigInteger::getBitRange (int startBit, int numBits) const +{ + BigInteger r; + numBits = bmin (numBits, getHighestBit() + 1 - startBit); + r.ensureSize ((size_t) bitToIndex (numBits)); + r.highestBit = numBits; + + int i = 0; + while (numBits > 0) + { + r.values[i++] = getBitRangeAsInt (startBit, (int) bmin (32, numBits)); + numBits -= 32; + startBit += 32; + } + + r.highestBit = r.getHighestBit(); + return r; +} + +uint32 BigInteger::getBitRangeAsInt (const int startBit, int numBits) const noexcept +{ + if (numBits > 32) + { + jassertfalse; // use getBitRange() if you need more than 32 bits.. + numBits = 32; + } + + numBits = bmin (numBits, highestBit + 1 - startBit); + + if (numBits <= 0) + return 0; + + const size_t pos = bitToIndex (startBit); + const int offset = startBit & 31; + const int endSpace = 32 - numBits; + + uint32 n = ((uint32) values [pos]) >> offset; + + if (offset > endSpace) + n |= ((uint32) values [pos + 1]) << (32 - offset); + + return n & (((uint32) 0xffffffff) >> endSpace); +} + +void BigInteger::setBitRangeAsInt (const int startBit, int numBits, uint32 valueToSet) +{ + if (numBits > 32) + { + jassertfalse; + numBits = 32; + } + + for (int i = 0; i < numBits; ++i) + { + setBit (startBit + i, (valueToSet & 1) != 0); + valueToSet >>= 1; + } +} + +//============================================================================== +void BigInteger::clear() +{ + if (numValues > 16) + { + numValues = 4; + values.calloc (numValues + 1); + } + else + { + values.clear (numValues + 1); + } + + highestBit = -1; + negative = false; +} + +void BigInteger::setBit (const int bit) +{ + if (bit >= 0) + { + if (bit > highestBit) + { + ensureSize (bitToIndex (bit)); + highestBit = bit; + } + + values [bitToIndex (bit)] |= bitToMask (bit); + } +} + +void BigInteger::setBit (const int bit, const bool shouldBeSet) +{ + if (shouldBeSet) + setBit (bit); + else + clearBit (bit); +} + +void BigInteger::clearBit (const int bit) noexcept +{ + if (bit >= 0 && bit <= highestBit) + values [bitToIndex (bit)] &= ~bitToMask (bit); +} + +void BigInteger::setRange (int startBit, int numBits, const bool shouldBeSet) +{ + while (--numBits >= 0) + setBit (startBit++, shouldBeSet); +} + +void BigInteger::insertBit (const int bit, const bool shouldBeSet) +{ + if (bit >= 0) + shiftBits (1, bit); + + setBit (bit, shouldBeSet); +} + +//============================================================================== +bool BigInteger::isZero() const noexcept +{ + return getHighestBit() < 0; +} + +bool BigInteger::isOne() const noexcept +{ + return getHighestBit() == 0 && ! negative; +} + +bool BigInteger::isNegative() const noexcept +{ + return negative && ! isZero(); +} + +void BigInteger::setNegative (const bool neg) noexcept +{ + negative = neg; +} + +void BigInteger::negate() noexcept +{ + negative = (! negative) && ! isZero(); +} + +#if BEAST_USE_INTRINSICS && ! defined (__INTEL_COMPILER) + #pragma intrinsic (_BitScanReverse) +#endif + +namespace BitFunctions +{ + inline int countBitsInInt32 (uint32 n) noexcept + { + n -= ((n >> 1) & 0x55555555); + n = (((n >> 2) & 0x33333333) + (n & 0x33333333)); + n = (((n >> 4) + n) & 0x0f0f0f0f); + n += (n >> 8); + n += (n >> 16); + return (int) (n & 0x3f); + } + + inline int highestBitInInt (uint32 n) noexcept + { + bassert (n != 0); // (the built-in functions may not work for n = 0) + + #if BEAST_GCC + return 31 - __builtin_clz (n); + #elif BEAST_USE_INTRINSICS + unsigned long highest; + _BitScanReverse (&highest, n); + return (int) highest; + #else + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return countBitsInInt32 (n >> 1); + #endif + } +} + +int BigInteger::countNumberOfSetBits() const noexcept +{ + int total = 0; + + for (int i = (int) bitToIndex (highestBit) + 1; --i >= 0;) + total += BitFunctions::countBitsInInt32 (values[i]); + + return total; +} + +int BigInteger::getHighestBit() const noexcept +{ + for (int i = (int) bitToIndex (highestBit + 1); i >= 0; --i) + { + const uint32 n = values[i]; + + if (n != 0) + return BitFunctions::highestBitInInt (n) + (i << 5); + } + + return -1; +} + +int BigInteger::findNextSetBit (int i) const noexcept +{ + for (; i <= highestBit; ++i) + if ((values [bitToIndex (i)] & bitToMask (i)) != 0) + return i; + + return -1; +} + +int BigInteger::findNextClearBit (int i) const noexcept +{ + for (; i <= highestBit; ++i) + if ((values [bitToIndex (i)] & bitToMask (i)) == 0) + break; + + return i; +} + +//============================================================================== +BigInteger& BigInteger::operator+= (const BigInteger& other) +{ + if (other.isNegative()) + return operator-= (-other); + + if (isNegative()) + { + if (compareAbsolute (other) < 0) + { + BigInteger temp (*this); + temp.negate(); + *this = other; + operator-= (temp); + } + else + { + negate(); + operator-= (other); + negate(); + } + } + else + { + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + ++highestBit; + + const size_t numInts = bitToIndex (highestBit) + 1; + ensureSize (numInts); + + int64 remainder = 0; + + for (size_t i = 0; i <= numInts; ++i) + { + if (i < numValues) + remainder += values[i]; + + if (i < other.numValues) + remainder += other.values[i]; + + values[i] = (uint32) remainder; + remainder >>= 32; + } + + bassert (remainder == 0); + highestBit = getHighestBit(); + } + + return *this; +} + +BigInteger& BigInteger::operator-= (const BigInteger& other) +{ + if (other.isNegative()) + return operator+= (-other); + + if (! isNegative()) + { + if (compareAbsolute (other) < 0) + { + BigInteger temp (other); + swapWith (temp); + operator-= (temp); + negate(); + return *this; + } + } + else + { + negate(); + operator+= (other); + negate(); + return *this; + } + + const size_t numInts = bitToIndex (highestBit) + 1; + const size_t maxOtherInts = bitToIndex (other.highestBit) + 1; + int64 amountToSubtract = 0; + + for (size_t i = 0; i <= numInts; ++i) + { + if (i <= maxOtherInts) + amountToSubtract += (int64) other.values[i]; + + if (values[i] >= amountToSubtract) + { + values[i] = (uint32) (values[i] - amountToSubtract); + amountToSubtract = 0; + } + else + { + const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; + values[i] = (uint32) n; + amountToSubtract = 1; + } + } + + return *this; +} + +BigInteger& BigInteger::operator*= (const BigInteger& other) +{ + BigInteger total; + highestBit = getHighestBit(); + const bool wasNegative = isNegative(); + setNegative (false); + + for (int i = 0; i <= highestBit; ++i) + { + if (operator[](i)) + { + BigInteger n (other); + n.setNegative (false); + n <<= i; + total += n; + } + } + + total.setNegative (wasNegative ^ other.isNegative()); + swapWith (total); + return *this; +} + +void BigInteger::divideBy (const BigInteger& divisor, BigInteger& remainder) +{ + bassert (this != &remainder); // (can't handle passing itself in to get the remainder) + + const int divHB = divisor.getHighestBit(); + const int ourHB = getHighestBit(); + + if (divHB < 0 || ourHB < 0) + { + // division by zero + remainder.clear(); + clear(); + } + else + { + const bool wasNegative = isNegative(); + + swapWith (remainder); + remainder.setNegative (false); + clear(); + + BigInteger temp (divisor); + temp.setNegative (false); + + int leftShift = ourHB - divHB; + temp <<= leftShift; + + while (leftShift >= 0) + { + if (remainder.compareAbsolute (temp) >= 0) + { + remainder -= temp; + setBit (leftShift); + } + + if (--leftShift >= 0) + temp >>= 1; + } + + negative = wasNegative ^ divisor.isNegative(); + remainder.setNegative (wasNegative); + } +} + +BigInteger& BigInteger::operator/= (const BigInteger& other) +{ + BigInteger remainder; + divideBy (other, remainder); + return *this; +} + +BigInteger& BigInteger::operator|= (const BigInteger& other) +{ + // this operation doesn't take into account negative values.. + bassert (isNegative() == other.isNegative()); + + if (other.highestBit >= 0) + { + ensureSize (bitToIndex (other.highestBit)); + + int n = (int) bitToIndex (other.highestBit) + 1; + + while (--n >= 0) + values[n] |= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); + } + + return *this; +} + +BigInteger& BigInteger::operator&= (const BigInteger& other) +{ + // this operation doesn't take into account negative values.. + bassert (isNegative() == other.isNegative()); + + int n = (int) numValues; + + while (n > (int) other.numValues) + values[--n] = 0; + + while (--n >= 0) + values[n] &= other.values[n]; + + if (other.highestBit < highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); + return *this; +} + +BigInteger& BigInteger::operator^= (const BigInteger& other) +{ + // this operation will only work with the absolute values + bassert (isNegative() == other.isNegative()); + + if (other.highestBit >= 0) + { + ensureSize (bitToIndex (other.highestBit)); + + int n = (int) bitToIndex (other.highestBit) + 1; + + while (--n >= 0) + values[n] ^= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); + } + + return *this; +} + +BigInteger& BigInteger::operator%= (const BigInteger& divisor) +{ + BigInteger remainder; + divideBy (divisor, remainder); + swapWith (remainder); + return *this; +} + +BigInteger& BigInteger::operator++() { return operator+= (1); } +BigInteger& BigInteger::operator--() { return operator-= (1); } +BigInteger BigInteger::operator++ (int) { const BigInteger old (*this); operator+= (1); return old; } +BigInteger BigInteger::operator-- (int) { const BigInteger old (*this); operator-= (1); return old; } + +BigInteger BigInteger::operator-() const { BigInteger b (*this); b.negate(); return b; } +BigInteger BigInteger::operator+ (const BigInteger& other) const { BigInteger b (*this); return b += other; } +BigInteger BigInteger::operator- (const BigInteger& other) const { BigInteger b (*this); return b -= other; } +BigInteger BigInteger::operator* (const BigInteger& other) const { BigInteger b (*this); return b *= other; } +BigInteger BigInteger::operator/ (const BigInteger& other) const { BigInteger b (*this); return b /= other; } +BigInteger BigInteger::operator| (const BigInteger& other) const { BigInteger b (*this); return b |= other; } +BigInteger BigInteger::operator& (const BigInteger& other) const { BigInteger b (*this); return b &= other; } +BigInteger BigInteger::operator^ (const BigInteger& other) const { BigInteger b (*this); return b ^= other; } +BigInteger BigInteger::operator% (const BigInteger& other) const { BigInteger b (*this); return b %= other; } +BigInteger BigInteger::operator<< (const int numBits) const { BigInteger b (*this); return b <<= numBits; } +BigInteger BigInteger::operator>> (const int numBits) const { BigInteger b (*this); return b >>= numBits; } +BigInteger& BigInteger::operator<<= (const int numBits) { shiftBits (numBits, 0); return *this; } +BigInteger& BigInteger::operator>>= (const int numBits) { shiftBits (-numBits, 0); return *this; } + +//============================================================================== +int BigInteger::compare (const BigInteger& other) const noexcept +{ + if (isNegative() == other.isNegative()) + { + const int absComp = compareAbsolute (other); + return isNegative() ? -absComp : absComp; + } + else + { + return isNegative() ? -1 : 1; + } +} + +int BigInteger::compareAbsolute (const BigInteger& other) const noexcept +{ + const int h1 = getHighestBit(); + const int h2 = other.getHighestBit(); + + if (h1 > h2) + return 1; + else if (h1 < h2) + return -1; + + for (int i = (int) bitToIndex (h1) + 1; --i >= 0;) + if (values[i] != other.values[i]) + return (values[i] > other.values[i]) ? 1 : -1; + + return 0; +} + +bool BigInteger::operator== (const BigInteger& other) const noexcept { return compare (other) == 0; } +bool BigInteger::operator!= (const BigInteger& other) const noexcept { return compare (other) != 0; } +bool BigInteger::operator< (const BigInteger& other) const noexcept { return compare (other) < 0; } +bool BigInteger::operator<= (const BigInteger& other) const noexcept { return compare (other) <= 0; } +bool BigInteger::operator> (const BigInteger& other) const noexcept { return compare (other) > 0; } +bool BigInteger::operator>= (const BigInteger& other) const noexcept { return compare (other) >= 0; } + +//============================================================================== +void BigInteger::shiftLeft (int bits, const int startBit) +{ + if (startBit > 0) + { + for (int i = highestBit + 1; --i >= startBit;) + setBit (i + bits, operator[] (i)); + + while (--bits >= 0) + clearBit (bits + startBit); + } + else + { + ensureSize (bitToIndex (highestBit + bits) + 1); + + const size_t wordsToMove = bitToIndex (bits); + size_t top = 1 + bitToIndex (highestBit); + highestBit += bits; + + if (wordsToMove > 0) + { + for (int i = (int) top; --i >= 0;) + values [(size_t) i + wordsToMove] = values [i]; + + for (size_t j = 0; j < wordsToMove; ++j) + values [j] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + for (size_t i = top + 1 + wordsToMove; --i > wordsToMove;) + values[i] = (values[i] << bits) | (values [i - 1] >> invBits); + + values [wordsToMove] = values [wordsToMove] << bits; + } + + highestBit = getHighestBit(); + } +} + +void BigInteger::shiftRight (int bits, const int startBit) +{ + if (startBit > 0) + { + for (int i = startBit; i <= highestBit; ++i) + setBit (i, operator[] (i + bits)); + + highestBit = getHighestBit(); + } + else + { + if (bits > highestBit) + { + clear(); + } + else + { + const size_t wordsToMove = bitToIndex (bits); + size_t top = 1 + bitToIndex (highestBit) - wordsToMove; + highestBit -= bits; + + if (wordsToMove > 0) + { + size_t i; + for (i = 0; i < top; ++i) + values [i] = values [i + wordsToMove]; + + for (i = 0; i < wordsToMove; ++i) + values [top + i] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + --top; + for (size_t i = 0; i < top; ++i) + values[i] = (values[i] >> bits) | (values [i + 1] << invBits); + + values[top] = (values[top] >> bits); + } + + highestBit = getHighestBit(); + } + } +} + +void BigInteger::shiftBits (int bits, const int startBit) +{ + if (highestBit >= 0) + { + if (bits < 0) + shiftRight (-bits, startBit); + else if (bits > 0) + shiftLeft (bits, startBit); + } +} + +//============================================================================== +static BigInteger simpleGCD (BigInteger* m, BigInteger* n) +{ + while (! m->isZero()) + { + if (n->compareAbsolute (*m) > 0) + std::swap (m, n); + + *m -= *n; + } + + return *n; +} + +BigInteger BigInteger::findGreatestCommonDivisor (BigInteger n) const +{ + BigInteger m (*this); + + while (! n.isZero()) + { + if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) + return simpleGCD (&m, &n); + + BigInteger temp2; + m.divideBy (n, temp2); + + m.swapWith (n); + n.swapWith (temp2); + } + + return m; +} + +void BigInteger::exponentModulo (const BigInteger& exponent, const BigInteger& modulus) +{ + BigInteger exp (exponent); + exp %= modulus; + + BigInteger value (1); + swapWith (value); + value %= modulus; + + while (! exp.isZero()) + { + if (exp [0]) + { + operator*= (value); + operator%= (modulus); + } + + value *= value; + value %= modulus; + exp >>= 1; + } +} + +void BigInteger::inverseModulo (const BigInteger& modulus) +{ + if (modulus.isOne() || modulus.isNegative()) + { + clear(); + return; + } + + if (isNegative() || compareAbsolute (modulus) >= 0) + operator%= (modulus); + + if (isOne()) + return; + + if (! (*this)[0]) + { + // not invertible + clear(); + return; + } + + BigInteger a1 (modulus); + BigInteger a2 (*this); + BigInteger b1 (modulus); + BigInteger b2 (1); + + while (! a2.isOne()) + { + BigInteger temp1, multiplier (a1); + multiplier.divideBy (a2, temp1); + + temp1 = a2; + temp1 *= multiplier; + BigInteger temp2 (a1); + temp2 -= temp1; + a1 = a2; + a2 = temp2; + + temp1 = b2; + temp1 *= multiplier; + temp2 = b1; + temp2 -= temp1; + b1 = b2; + b2 = temp2; + } + + while (b2.isNegative()) + b2 += modulus; + + b2 %= modulus; + swapWith (b2); +} + +//============================================================================== +OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const BigInteger& value) +{ + return stream << value.toString (10); +} + +String BigInteger::toString (const int base, const int minimumNumCharacters) const +{ + String s; + BigInteger v (*this); + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + static const char hexDigits[] = "0123456789abcdef"; + + for (;;) + { + const uint32 remainder = v.getBitRangeAsInt (0, bits); + + v >>= bits; + + if (remainder == 0 && v.isZero()) + break; + + s = String::charToString ((beast_wchar) (uint8) hexDigits [remainder]) + s; + } + } + else if (base == 10) + { + const BigInteger ten (10); + BigInteger remainder; + + for (;;) + { + v.divideBy (ten, remainder); + + if (remainder.isZero() && v.isZero()) + break; + + s = String (remainder.getBitRangeAsInt (0, 8)) + s; + } + } + else + { + jassertfalse; // can't do the specified base! + return String::empty; + } + + s = s.paddedLeft ('0', minimumNumCharacters); + + return isNegative() ? "-" + s : s; +} + +void BigInteger::parseString (const String& text, const int base) +{ + clear(); + String::CharPointerType t (text.getCharPointer()); + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + + for (;;) + { + const beast_wchar c = t.getAndAdvance(); + const int digit = CharacterFunctions::getHexDigitValue (c); + + if (((uint32) digit) < (uint32) base) + { + operator<<= (bits); + operator+= (digit); + } + else if (c == 0) + { + break; + } + } + } + else if (base == 10) + { + const BigInteger ten ((uint32) 10); + + for (;;) + { + const beast_wchar c = t.getAndAdvance(); + + if (c >= '0' && c <= '9') + { + operator*= (ten); + operator+= ((int) (c - '0')); + } + else if (c == 0) + { + break; + } + } + } + + setNegative (text.trimStart().startsWithChar ('-')); +} + +MemoryBlock BigInteger::toMemoryBlock() const +{ + const int numBytes = (getHighestBit() + 8) >> 3; + MemoryBlock mb ((size_t) numBytes); + + for (int i = 0; i < numBytes; ++i) + mb[i] = (char) getBitRangeAsInt (i << 3, 8); + + return mb; +} + +void BigInteger::loadFromMemoryBlock (const MemoryBlock& data) +{ + clear(); + + for (int i = (int) data.getSize(); --i >= 0;) + this->setBitRangeAsInt (i << 3, 8, (uint32) data [i]); +} diff --git a/modules/beast_core/maths/beast_BigInteger.h b/modules/beast_core/maths/beast_BigInteger.h new file mode 100644 index 0000000000..c5737d8179 --- /dev/null +++ b/modules/beast_core/maths/beast_BigInteger.h @@ -0,0 +1,329 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_BIGINTEGER_BEASTHEADER +#define BEAST_BIGINTEGER_BEASTHEADER + +#include "../text/beast_String.h" +#include "../memory/beast_HeapBlock.h" +class MemoryBlock; + + +//============================================================================== +/** + An arbitrarily large integer class. + + A BigInteger can be used in a similar way to a normal integer, but has no size + limit (except for memory and performance constraints). + + Negative values are possible, but the value isn't stored as 2s-complement, so + be careful if you use negative values and look at the values of individual bits. +*/ +class BEAST_API BigInteger +{ +public: + //============================================================================== + /** Creates an empty BigInteger */ + BigInteger(); + + /** Creates a BigInteger containing an integer value in its low bits. + The low 32 bits of the number are initialised with this value. + */ + BigInteger (uint32 value); + + /** Creates a BigInteger containing an integer value in its low bits. + The low 32 bits of the number are initialised with the absolute value + passed in, and its sign is set to reflect the sign of the number. + */ + BigInteger (int32 value); + + /** Creates a BigInteger containing an integer value in its low bits. + The low 64 bits of the number are initialised with the absolute value + passed in, and its sign is set to reflect the sign of the number. + */ + BigInteger (int64 value); + + /** Creates a copy of another BigInteger. */ + BigInteger (const BigInteger& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + BigInteger (BigInteger&& other) noexcept; + BigInteger& operator= (BigInteger&& other) noexcept; + #endif + + /** Destructor. */ + ~BigInteger(); + + //============================================================================== + /** Copies another BigInteger onto this one. */ + BigInteger& operator= (const BigInteger& other); + + /** Swaps the internal contents of this with another object. */ + void swapWith (BigInteger& other) noexcept; + + //============================================================================== + /** Returns the value of a specified bit in the number. + If the index is out-of-range, the result will be false. + */ + bool operator[] (int bit) const noexcept; + + /** Returns true if no bits are set. */ + bool isZero() const noexcept; + + /** Returns true if the value is 1. */ + bool isOne() const noexcept; + + /** Attempts to get the lowest bits of the value as an integer. + If the value is bigger than the integer limits, this will return only the lower bits. + */ + int toInteger() const noexcept; + + //============================================================================== + /** Resets the value to 0. */ + void clear(); + + /** Clears a particular bit in the number. */ + void clearBit (int bitNumber) noexcept; + + /** Sets a specified bit to 1. */ + void setBit (int bitNumber); + + /** Sets or clears a specified bit. */ + void setBit (int bitNumber, bool shouldBeSet); + + /** Sets a range of bits to be either on or off. + + @param startBit the first bit to change + @param numBits the number of bits to change + @param shouldBeSet whether to turn these bits on or off + */ + void setRange (int startBit, int numBits, bool shouldBeSet); + + /** Inserts a bit an a given position, shifting up any bits above it. */ + void insertBit (int bitNumber, bool shouldBeSet); + + /** Returns a range of bits as a new BigInteger. + + e.g. getBitRangeAsInt (0, 64) would return the lowest 64 bits. + @see getBitRangeAsInt + */ + BigInteger getBitRange (int startBit, int numBits) const; + + /** Returns a range of bits as an integer value. + + e.g. getBitRangeAsInt (0, 32) would return the lowest 32 bits. + + Asking for more than 32 bits isn't allowed (obviously) - for that, use + getBitRange(). + */ + uint32 getBitRangeAsInt (int startBit, int numBits) const noexcept; + + /** Sets a range of bits to an integer value. + + Copies the given integer onto a range of bits, starting at startBit, + and using up to numBits of the available bits. + */ + void setBitRangeAsInt (int startBit, int numBits, uint32 valueToSet); + + /** Shifts a section of bits left or right. + + @param howManyBitsLeft how far to move the bits (+ve numbers shift it left, -ve numbers shift it right). + @param startBit the first bit to affect - if this is > 0, only bits above that index will be affected. + */ + void shiftBits (int howManyBitsLeft, int startBit); + + /** Returns the total number of set bits in the value. */ + int countNumberOfSetBits() const noexcept; + + /** Looks for the index of the next set bit after a given starting point. + + This searches from startIndex (inclusive) upwards for the first set bit, + and returns its index. If no set bits are found, it returns -1. + */ + int findNextSetBit (int startIndex) const noexcept; + + /** Looks for the index of the next clear bit after a given starting point. + + This searches from startIndex (inclusive) upwards for the first clear bit, + and returns its index. + */ + int findNextClearBit (int startIndex) const noexcept; + + /** Returns the index of the highest set bit in the number. + If the value is zero, this will return -1. + */ + int getHighestBit() const noexcept; + + //============================================================================== + // All the standard arithmetic ops... + + BigInteger& operator+= (const BigInteger& other); + BigInteger& operator-= (const BigInteger& other); + BigInteger& operator*= (const BigInteger& other); + BigInteger& operator/= (const BigInteger& other); + BigInteger& operator|= (const BigInteger& other); + BigInteger& operator&= (const BigInteger& other); + BigInteger& operator^= (const BigInteger& other); + BigInteger& operator%= (const BigInteger& other); + BigInteger& operator<<= (int numBitsToShift); + BigInteger& operator>>= (int numBitsToShift); + BigInteger& operator++(); + BigInteger& operator--(); + BigInteger operator++ (int); + BigInteger operator-- (int); + + BigInteger operator-() const; + BigInteger operator+ (const BigInteger& other) const; + BigInteger operator- (const BigInteger& other) const; + BigInteger operator* (const BigInteger& other) const; + BigInteger operator/ (const BigInteger& other) const; + BigInteger operator| (const BigInteger& other) const; + BigInteger operator& (const BigInteger& other) const; + BigInteger operator^ (const BigInteger& other) const; + BigInteger operator% (const BigInteger& other) const; + BigInteger operator<< (int numBitsToShift) const; + BigInteger operator>> (int numBitsToShift) const; + + bool operator== (const BigInteger& other) const noexcept; + bool operator!= (const BigInteger& other) const noexcept; + bool operator< (const BigInteger& other) const noexcept; + bool operator<= (const BigInteger& other) const noexcept; + bool operator> (const BigInteger& other) const noexcept; + bool operator>= (const BigInteger& other) const noexcept; + + //============================================================================== + /** Does a signed comparison of two BigIntegers. + + Return values are: + - 0 if the numbers are the same + - < 0 if this number is smaller than the other + - > 0 if this number is bigger than the other + */ + int compare (const BigInteger& other) const noexcept; + + /** Compares the magnitudes of two BigIntegers, ignoring their signs. + + Return values are: + - 0 if the numbers are the same + - < 0 if this number is smaller than the other + - > 0 if this number is bigger than the other + */ + int compareAbsolute (const BigInteger& other) const noexcept; + + /** Divides this value by another one and returns the remainder. + + This number is divided by other, leaving the quotient in this number, + with the remainder being copied to the other BigInteger passed in. + */ + void divideBy (const BigInteger& divisor, BigInteger& remainder); + + /** Returns the largest value that will divide both this value and the one passed-in. + */ + BigInteger findGreatestCommonDivisor (BigInteger other) const; + + /** Performs a combined exponent and modulo operation. + This BigInteger's value becomes (this ^ exponent) % modulus. + */ + void exponentModulo (const BigInteger& exponent, const BigInteger& modulus); + + /** Performs an inverse modulo on the value. + i.e. the result is (this ^ -1) mod (modulus). + */ + void inverseModulo (const BigInteger& modulus); + + //============================================================================== + /** Returns true if the value is less than zero. + @see setNegative, negate + */ + bool isNegative() const noexcept; + + /** Changes the sign of the number to be positive or negative. + @see isNegative, negate + */ + void setNegative (bool shouldBeNegative) noexcept; + + /** Inverts the sign of the number. + @see isNegative, setNegative + */ + void negate() noexcept; + + //============================================================================== + /** Converts the number to a string. + + Specify a base such as 2 (binary), 8 (octal), 10 (decimal), 16 (hex). + If minimumNumCharacters is greater than 0, the returned string will be + padded with leading zeros to reach at least that length. + */ + String toString (int base, int minimumNumCharacters = 1) const; + + /** Reads the numeric value from a string. + + Specify a base such as 2 (binary), 8 (octal), 10 (decimal), 16 (hex). + Any invalid characters will be ignored. + */ + void parseString (const String& text, int base); + + //============================================================================== + /** Turns the number into a block of binary data. + + The data is arranged as little-endian, so the first byte of data is the low 8 bits + of the number, and so on. + + @see loadFromMemoryBlock + */ + MemoryBlock toMemoryBlock() const; + + /** Converts a block of raw data into a number. + + The data is arranged as little-endian, so the first byte of data is the low 8 bits + of the number, and so on. + + @see toMemoryBlock + */ + void loadFromMemoryBlock (const MemoryBlock& data); + +private: + //============================================================================== + HeapBlock values; + size_t numValues; + int highestBit; + bool negative; + + void ensureSize (size_t numVals); + void shiftLeft (int bits, int startBit); + void shiftRight (int bits, int startBit); + + BEAST_LEAK_DETECTOR (BigInteger) +}; + +/** Writes a BigInteger to an OutputStream as a UTF8 decimal string. */ +OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const BigInteger& value); + +//============================================================================== +#ifndef DOXYGEN + // For backwards compatibility, BitArray is defined as an alias for BigInteger. + typedef BigInteger BitArray; +#endif + + +#endif // BEAST_BIGINTEGER_BEASTHEADER diff --git a/modules/beast_core/maths/beast_Expression.cpp b/modules/beast_core/maths/beast_Expression.cpp new file mode 100644 index 0000000000..13c0640633 --- /dev/null +++ b/modules/beast_core/maths/beast_Expression.cpp @@ -0,0 +1,1180 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +class Expression::Term : public SingleThreadedReferenceCountedObject +{ +public: + Term() {} + virtual ~Term() {} + + virtual Type getType() const noexcept = 0; + virtual Term* clone() const = 0; + virtual ReferenceCountedObjectPtr resolve (const Scope&, int recursionDepth) = 0; + virtual String toString() const = 0; + virtual double toDouble() const { return 0; } + virtual int getInputIndexFor (const Term*) const { return -1; } + virtual int getOperatorPrecedence() const { return 0; } + virtual int getNumInputs() const { return 0; } + virtual Term* getInput (int) const { return nullptr; } + virtual ReferenceCountedObjectPtr negated(); + + virtual ReferenceCountedObjectPtr createTermToEvaluateInput (const Scope&, const Term* /*inputTerm*/, + double /*overallTarget*/, Term* /*topLevelTerm*/) const + { + jassertfalse; + return ReferenceCountedObjectPtr(); + } + + virtual String getName() const + { + jassertfalse; // You shouldn't call this for an expression that's not actually a function! + return String::empty; + } + + virtual void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) + { + for (int i = getNumInputs(); --i >= 0;) + getInput (i)->renameSymbol (oldSymbol, newName, scope, recursionDepth); + } + + class SymbolVisitor + { + public: + virtual ~SymbolVisitor() {} + virtual void useSymbol (const Symbol&) = 0; + }; + + virtual void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) + { + for (int i = getNumInputs(); --i >= 0;) + getInput(i)->visitAllSymbols (visitor, scope, recursionDepth); + } + +private: + BEAST_DECLARE_NON_COPYABLE (Term) +}; + + +//============================================================================== +struct Expression::Helpers +{ + typedef ReferenceCountedObjectPtr TermPtr; + + static void checkRecursionDepth (const int depth) + { + if (depth > 256) + throw EvaluationError ("Recursive symbol references"); + } + + friend class Expression::Term; + + //============================================================================== + /** An exception that can be thrown by Expression::evaluate(). */ + class EvaluationError : public std::exception + { + public: + EvaluationError (const String& desc) : description (desc) + { + DBG ("Expression::EvaluationError: " + description); + } + + String description; + }; + + //============================================================================== + class Constant : public Term + { + public: + Constant (const double val, const bool resolutionTarget) + : value (val), isResolutionTarget (resolutionTarget) {} + + Type getType() const noexcept { return constantType; } + Term* clone() const { return new Constant (value, isResolutionTarget); } + TermPtr resolve (const Scope&, int) { return this; } + double toDouble() const { return value; } + TermPtr negated() { return new Constant (-value, isResolutionTarget); } + + String toString() const + { + String s (value); + if (isResolutionTarget) + s = "@" + s; + + return s; + } + + double value; + bool isResolutionTarget; + }; + + //============================================================================== + class BinaryTerm : public Term + { + public: + BinaryTerm (Term* const l, Term* const r) : left (l), right (r) + { + bassert (l != nullptr && r != nullptr); + } + + int getInputIndexFor (const Term* possibleInput) const + { + return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1); + } + + Type getType() const noexcept { return operatorType; } + int getNumInputs() const { return 2; } + Term* getInput (int index) const { return index == 0 ? left.get() : (index == 1 ? right.get() : 0); } + + virtual double performFunction (double left, double right) const = 0; + virtual void writeOperator (String& dest) const = 0; + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + return new Constant (performFunction (left ->resolve (scope, recursionDepth)->toDouble(), + right->resolve (scope, recursionDepth)->toDouble()), false); + } + + String toString() const + { + String s; + + const int ourPrecendence = getOperatorPrecedence(); + if (left->getOperatorPrecedence() > ourPrecendence) + s << '(' << left->toString() << ')'; + else + s = left->toString(); + + writeOperator (s); + + if (right->getOperatorPrecedence() >= ourPrecendence) + s << '(' << right->toString() << ')'; + else + s << right->toString(); + + return s; + } + + protected: + const TermPtr left, right; + + TermPtr createDestinationTerm (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + bassert (input == left || input == right); + if (input != left && input != right) + return TermPtr(); + + const Term* const dest = findDestinationFor (topLevelTerm, this); + + if (dest == nullptr) + return new Constant (overallTarget, false); + + return dest->createTermToEvaluateInput (scope, this, overallTarget, topLevelTerm); + } + }; + + //============================================================================== + class SymbolTerm : public Term + { + public: + explicit SymbolTerm (const String& sym) : symbol (sym) {} + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + return scope.getSymbolValue (symbol).term->resolve (scope, recursionDepth + 1); + } + + Type getType() const noexcept { return symbolType; } + Term* clone() const { return new SymbolTerm (symbol); } + String toString() const { return symbol; } + String getName() const { return symbol; } + + void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + visitor.useSymbol (Symbol (scope.getScopeUID(), symbol)); + scope.getSymbolValue (symbol).term->visitAllSymbols (visitor, scope, recursionDepth + 1); + } + + void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int /*recursionDepth*/) + { + if (oldSymbol.symbolName == symbol && scope.getScopeUID() == oldSymbol.scopeUID) + symbol = newName; + } + + String symbol; + }; + + //============================================================================== + class Function : public Term + { + public: + explicit Function (const String& name) : functionName (name) {} + + Function (const String& name, const Array& params) + : functionName (name), parameters (params) + {} + + Type getType() const noexcept { return functionType; } + Term* clone() const { return new Function (functionName, parameters); } + int getNumInputs() const { return parameters.size(); } + Term* getInput (int i) const { return parameters.getReference(i).term; } + String getName() const { return functionName; } + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + double result = 0; + const int numParams = parameters.size(); + if (numParams > 0) + { + HeapBlock params ((size_t) numParams); + for (int i = 0; i < numParams; ++i) + params[i] = parameters.getReference(i).term->resolve (scope, recursionDepth + 1)->toDouble(); + + result = scope.evaluateFunction (functionName, params, numParams); + } + else + { + result = scope.evaluateFunction (functionName, nullptr, 0); + } + + return new Constant (result, false); + } + + int getInputIndexFor (const Term* possibleInput) const + { + for (int i = 0; i < parameters.size(); ++i) + if (parameters.getReference(i).term == possibleInput) + return i; + + return -1; + } + + String toString() const + { + if (parameters.size() == 0) + return functionName + "()"; + + String s (functionName + " ("); + + for (int i = 0; i < parameters.size(); ++i) + { + s << parameters.getReference(i).term->toString(); + + if (i < parameters.size() - 1) + s << ", "; + } + + s << ')'; + return s; + } + + const String functionName; + Array parameters; + }; + + //============================================================================== + class DotOperator : public BinaryTerm + { + public: + DotOperator (SymbolTerm* const l, Term* const r) : BinaryTerm (l, r) {} + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + + EvaluationVisitor visitor (right, recursionDepth + 1); + scope.visitRelativeScope (getSymbol()->symbol, visitor); + return visitor.output; + } + + Term* clone() const { return new DotOperator (getSymbol(), right); } + String getName() const { return "."; } + int getOperatorPrecedence() const { return 1; } + void writeOperator (String& dest) const { dest << '.'; } + double performFunction (double, double) const { return 0.0; } + + void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + visitor.useSymbol (Symbol (scope.getScopeUID(), getSymbol()->symbol)); + + SymbolVisitingVisitor v (right, visitor, recursionDepth + 1); + + try + { + scope.visitRelativeScope (getSymbol()->symbol, v); + } + catch (...) {} + } + + void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + getSymbol()->renameSymbol (oldSymbol, newName, scope, recursionDepth); + + SymbolRenamingVisitor visitor (right, oldSymbol, newName, recursionDepth + 1); + + try + { + scope.visitRelativeScope (getSymbol()->symbol, visitor); + } + catch (...) {} + } + + private: + //============================================================================== + class EvaluationVisitor : public Scope::Visitor + { + public: + EvaluationVisitor (const TermPtr& t, const int recursion) + : input (t), output (t), recursionCount (recursion) {} + + void visit (const Scope& scope) { output = input->resolve (scope, recursionCount); } + + const TermPtr input; + TermPtr output; + const int recursionCount; + + private: + BEAST_DECLARE_NON_COPYABLE (EvaluationVisitor) + }; + + class SymbolVisitingVisitor : public Scope::Visitor + { + public: + SymbolVisitingVisitor (const TermPtr& t, SymbolVisitor& v, const int recursion) + : input (t), visitor (v), recursionCount (recursion) {} + + void visit (const Scope& scope) { input->visitAllSymbols (visitor, scope, recursionCount); } + + private: + const TermPtr input; + SymbolVisitor& visitor; + const int recursionCount; + + BEAST_DECLARE_NON_COPYABLE (SymbolVisitingVisitor) + }; + + class SymbolRenamingVisitor : public Scope::Visitor + { + public: + SymbolRenamingVisitor (const TermPtr& t, const Expression::Symbol& symbol_, const String& newName_, const int recursionCount_) + : input (t), symbol (symbol_), newName (newName_), recursionCount (recursionCount_) {} + + void visit (const Scope& scope) { input->renameSymbol (symbol, newName, scope, recursionCount); } + + private: + const TermPtr input; + const Symbol& symbol; + const String newName; + const int recursionCount; + + BEAST_DECLARE_NON_COPYABLE (SymbolRenamingVisitor) + }; + + SymbolTerm* getSymbol() const { return static_cast (left.get()); } + + BEAST_DECLARE_NON_COPYABLE (DotOperator) + }; + + //============================================================================== + class Negate : public Term + { + public: + explicit Negate (const TermPtr& t) : input (t) + { + bassert (t != nullptr); + } + + Type getType() const noexcept { return operatorType; } + int getInputIndexFor (const Term* possibleInput) const { return possibleInput == input ? 0 : -1; } + int getNumInputs() const { return 1; } + Term* getInput (int index) const { return index == 0 ? input.get() : nullptr; } + Term* clone() const { return new Negate (input->clone()); } + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + return new Constant (-input->resolve (scope, recursionDepth)->toDouble(), false); + } + + String getName() const { return "-"; } + TermPtr negated() { return input; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* t, double overallTarget, Term* topLevelTerm) const + { + (void) t; + bassert (t == input); + + const Term* const dest = findDestinationFor (topLevelTerm, this); + + return new Negate (dest == nullptr ? new Constant (overallTarget, false) + : dest->createTermToEvaluateInput (scope, this, overallTarget, topLevelTerm)); + } + + String toString() const + { + if (input->getOperatorPrecedence() > 0) + return "-(" + input->toString() + ")"; + + return "-" + input->toString(); + } + + private: + const TermPtr input; + }; + + //============================================================================== + class Add : public BinaryTerm + { + public: + Add (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Add (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs + rhs; } + int getOperatorPrecedence() const { return 3; } + String getName() const { return "+"; } + void writeOperator (String& dest) const { dest << " + "; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + return new Subtract (newDest, (input == left ? right : left)->clone()); + } + + private: + BEAST_DECLARE_NON_COPYABLE (Add) + }; + + //============================================================================== + class Subtract : public BinaryTerm + { + public: + Subtract (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Subtract (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs - rhs; } + int getOperatorPrecedence() const { return 3; } + String getName() const { return "-"; } + void writeOperator (String& dest) const { dest << " - "; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + if (input == left) + return new Add (newDest, right->clone()); + + return new Subtract (left->clone(), newDest); + } + + private: + BEAST_DECLARE_NON_COPYABLE (Subtract) + }; + + //============================================================================== + class Multiply : public BinaryTerm + { + public: + Multiply (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Multiply (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs * rhs; } + String getName() const { return "*"; } + void writeOperator (String& dest) const { dest << " * "; } + int getOperatorPrecedence() const { return 2; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + return new Divide (newDest, (input == left ? right : left)->clone()); + } + + private: + BEAST_DECLARE_NON_COPYABLE (Multiply) + }; + + //============================================================================== + class Divide : public BinaryTerm + { + public: + Divide (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Divide (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs / rhs; } + String getName() const { return "/"; } + void writeOperator (String& dest) const { dest << " / "; } + int getOperatorPrecedence() const { return 2; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + if (input == left) + return new Multiply (newDest, right->clone()); + + return new Divide (left->clone(), newDest); + } + + private: + BEAST_DECLARE_NON_COPYABLE (Divide) + }; + + //============================================================================== + static Term* findDestinationFor (Term* const topLevel, const Term* const inputTerm) + { + const int inputIndex = topLevel->getInputIndexFor (inputTerm); + if (inputIndex >= 0) + return topLevel; + + for (int i = topLevel->getNumInputs(); --i >= 0;) + { + Term* const t = findDestinationFor (topLevel->getInput (i), inputTerm); + + if (t != nullptr) + return t; + } + + return nullptr; + } + + static Constant* findTermToAdjust (Term* const term, const bool mustBeFlagged) + { + bassert (term != nullptr); + + if (term->getType() == constantType) + { + Constant* const c = static_cast (term); + if (c->isResolutionTarget || ! mustBeFlagged) + return c; + } + + if (term->getType() == functionType) + return nullptr; + + const int numIns = term->getNumInputs(); + + for (int i = 0; i < numIns; ++i) + { + Term* const input = term->getInput (i); + + if (input->getType() == constantType) + { + Constant* const c = static_cast (input); + + if (c->isResolutionTarget || ! mustBeFlagged) + return c; + } + } + + for (int i = 0; i < numIns; ++i) + { + Constant* const c = findTermToAdjust (term->getInput (i), mustBeFlagged); + if (c != nullptr) + return c; + } + + return nullptr; + } + + static bool containsAnySymbols (const Term* const t) + { + if (t->getType() == Expression::symbolType) + return true; + + for (int i = t->getNumInputs(); --i >= 0;) + if (containsAnySymbols (t->getInput (i))) + return true; + + return false; + } + + //============================================================================== + class SymbolCheckVisitor : public Term::SymbolVisitor + { + public: + SymbolCheckVisitor (const Symbol& symbol_) : wasFound (false), symbol (symbol_) {} + void useSymbol (const Symbol& s) { wasFound = wasFound || s == symbol; } + + bool wasFound; + + private: + const Symbol& symbol; + + BEAST_DECLARE_NON_COPYABLE (SymbolCheckVisitor) + }; + + //============================================================================== + class SymbolListVisitor : public Term::SymbolVisitor + { + public: + SymbolListVisitor (Array& list_) : list (list_) {} + void useSymbol (const Symbol& s) { list.addIfNotAlreadyThere (s); } + + private: + Array& list; + + BEAST_DECLARE_NON_COPYABLE (SymbolListVisitor) + }; + + //============================================================================== + class Parser + { + public: + //============================================================================== + Parser (String::CharPointerType& stringToParse) + : text (stringToParse) + { + } + + TermPtr readUpToComma() + { + if (text.isEmpty()) + return new Constant (0.0, false); + + const TermPtr e (readExpression()); + + if (e == nullptr || ((! readOperator (",")) && ! text.isEmpty())) + throw ParseError ("Syntax error: \"" + String (text) + "\""); + + return e; + } + + private: + String::CharPointerType& text; + + //============================================================================== + static inline bool isDecimalDigit (const beast_wchar c) noexcept + { + return c >= '0' && c <= '9'; + } + + bool readChar (const beast_wchar required) noexcept + { + if (*text == required) + { + ++text; + return true; + } + + return false; + } + + bool readOperator (const char* ops, char* const opType = nullptr) noexcept + { + text = text.findEndOfWhitespace(); + + while (*ops != 0) + { + if (readChar ((beast_wchar) (uint8) *ops)) + { + if (opType != nullptr) + *opType = *ops; + + return true; + } + + ++ops; + } + + return false; + } + + bool readIdentifier (String& identifier) noexcept + { + text = text.findEndOfWhitespace(); + String::CharPointerType t (text); + int numChars = 0; + + if (t.isLetter() || *t == '_') + { + ++t; + ++numChars; + + while (t.isLetterOrDigit() || *t == '_') + { + ++t; + ++numChars; + } + } + + if (numChars > 0) + { + identifier = String (text, (size_t) numChars); + text = t; + return true; + } + + return false; + } + + Term* readNumber() noexcept + { + text = text.findEndOfWhitespace(); + String::CharPointerType t (text); + + const bool isResolutionTarget = (*t == '@'); + if (isResolutionTarget) + { + ++t; + t = t.findEndOfWhitespace(); + text = t; + } + + if (*t == '-') + { + ++t; + t = t.findEndOfWhitespace(); + } + + if (isDecimalDigit (*t) || (*t == '.' && isDecimalDigit (t[1]))) + return new Constant (CharacterFunctions::readDoubleValue (text), isResolutionTarget); + + return nullptr; + } + + TermPtr readExpression() + { + TermPtr lhs (readMultiplyOrDivideExpression()); + + char opType; + while (lhs != nullptr && readOperator ("+-", &opType)) + { + TermPtr rhs (readMultiplyOrDivideExpression()); + + if (rhs == nullptr) + throw ParseError ("Expected expression after \"" + String::charToString ((beast_wchar) (uint8) opType) + "\""); + + if (opType == '+') + lhs = new Add (lhs, rhs); + else + lhs = new Subtract (lhs, rhs); + } + + return lhs; + } + + TermPtr readMultiplyOrDivideExpression() + { + TermPtr lhs (readUnaryExpression()); + + char opType; + while (lhs != nullptr && readOperator ("*/", &opType)) + { + TermPtr rhs (readUnaryExpression()); + + if (rhs == nullptr) + throw ParseError ("Expected expression after \"" + String::charToString ((beast_wchar) (uint8) opType) + "\""); + + if (opType == '*') + lhs = new Multiply (lhs, rhs); + else + lhs = new Divide (lhs, rhs); + } + + return lhs; + } + + TermPtr readUnaryExpression() + { + char opType; + if (readOperator ("+-", &opType)) + { + TermPtr e (readUnaryExpression()); + + if (e == nullptr) + throw ParseError ("Expected expression after \"" + String::charToString ((beast_wchar) (uint8) opType) + "\""); + + if (opType == '-') + e = e->negated(); + + return e; + } + + return readPrimaryExpression(); + } + + TermPtr readPrimaryExpression() + { + TermPtr e (readParenthesisedExpression()); + if (e != nullptr) + return e; + + e = readNumber(); + if (e != nullptr) + return e; + + return readSymbolOrFunction(); + } + + TermPtr readSymbolOrFunction() + { + String identifier; + if (readIdentifier (identifier)) + { + if (readOperator ("(")) // method call... + { + Function* const f = new Function (identifier); + ScopedPointer func (f); // (can't use ScopedPointer in MSVC) + + TermPtr param (readExpression()); + + if (param == nullptr) + { + if (readOperator (")")) + return func.release(); + + throw ParseError ("Expected parameters after \"" + identifier + " (\""); + } + + f->parameters.add (Expression (param)); + + while (readOperator (",")) + { + param = readExpression(); + + if (param == nullptr) + throw ParseError ("Expected expression after \",\""); + + f->parameters.add (Expression (param)); + } + + if (readOperator (")")) + return func.release(); + + throw ParseError ("Expected \")\""); + } + + if (readOperator (".")) + { + TermPtr rhs (readSymbolOrFunction()); + + if (rhs == nullptr) + throw ParseError ("Expected symbol or function after \".\""); + + if (identifier == "this") + return rhs; + + return new DotOperator (new SymbolTerm (identifier), rhs); + } + + // just a symbol.. + bassert (identifier.trim() == identifier); + return new SymbolTerm (identifier); + } + + return TermPtr(); + } + + TermPtr readParenthesisedExpression() + { + if (! readOperator ("(")) + return TermPtr(); + + const TermPtr e (readExpression()); + if (e == nullptr || ! readOperator (")")) + return TermPtr(); + + return e; + } + + BEAST_DECLARE_NON_COPYABLE (Parser) + }; +}; + +//============================================================================== +Expression::Expression() + : term (new Expression::Helpers::Constant (0, false)) +{ +} + +Expression::~Expression() +{ +} + +Expression::Expression (Term* const term_) + : term (term_) +{ + bassert (term != nullptr); +} + +Expression::Expression (const double constant) + : term (new Expression::Helpers::Constant (constant, false)) +{ +} + +Expression::Expression (const Expression& other) + : term (other.term) +{ +} + +Expression& Expression::operator= (const Expression& other) +{ + term = other.term; + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +Expression::Expression (Expression&& other) noexcept + : term (static_cast &&> (other.term)) +{ +} + +Expression& Expression::operator= (Expression&& other) noexcept +{ + term = static_cast &&> (other.term); + return *this; +} +#endif + +Expression::Expression (const String& stringToParse) +{ + String::CharPointerType text (stringToParse.getCharPointer()); + Helpers::Parser parser (text); + term = parser.readUpToComma(); +} + +Expression Expression::parse (String::CharPointerType& stringToParse) +{ + Helpers::Parser parser (stringToParse); + return Expression (parser.readUpToComma()); +} + +double Expression::evaluate() const +{ + return evaluate (Expression::Scope()); +} + +double Expression::evaluate (const Expression::Scope& scope) const +{ + try + { + return term->resolve (scope, 0)->toDouble(); + } + catch (Helpers::EvaluationError&) + {} + + return 0; +} + +double Expression::evaluate (const Scope& scope, String& evaluationError) const +{ + try + { + return term->resolve (scope, 0)->toDouble(); + } + catch (Helpers::EvaluationError& e) + { + evaluationError = e.description; + } + + return 0; +} + +Expression Expression::operator+ (const Expression& other) const { return Expression (new Helpers::Add (term, other.term)); } +Expression Expression::operator- (const Expression& other) const { return Expression (new Helpers::Subtract (term, other.term)); } +Expression Expression::operator* (const Expression& other) const { return Expression (new Helpers::Multiply (term, other.term)); } +Expression Expression::operator/ (const Expression& other) const { return Expression (new Helpers::Divide (term, other.term)); } +Expression Expression::operator-() const { return Expression (term->negated()); } +Expression Expression::symbol (const String& symbol) { return Expression (new Helpers::SymbolTerm (symbol)); } + +Expression Expression::function (const String& functionName, const Array& parameters) +{ + return Expression (new Helpers::Function (functionName, parameters)); +} + +Expression Expression::adjustedToGiveNewResult (const double targetValue, const Expression::Scope& scope) const +{ + ScopedPointer newTerm (term->clone()); + + Helpers::Constant* termToAdjust = Helpers::findTermToAdjust (newTerm, true); + + if (termToAdjust == nullptr) + termToAdjust = Helpers::findTermToAdjust (newTerm, false); + + if (termToAdjust == nullptr) + { + newTerm = new Helpers::Add (newTerm.release(), new Helpers::Constant (0, false)); + termToAdjust = Helpers::findTermToAdjust (newTerm, false); + } + + bassert (termToAdjust != nullptr); + + const Term* const parent = Helpers::findDestinationFor (newTerm, termToAdjust); + + if (parent == nullptr) + { + termToAdjust->value = targetValue; + } + else + { + const Helpers::TermPtr reverseTerm (parent->createTermToEvaluateInput (scope, termToAdjust, targetValue, newTerm)); + + if (reverseTerm == nullptr) + return Expression (targetValue); + + termToAdjust->value = reverseTerm->resolve (scope, 0)->toDouble(); + } + + return Expression (newTerm.release()); +} + +Expression Expression::withRenamedSymbol (const Expression::Symbol& oldSymbol, const String& newName, const Scope& scope) const +{ + bassert (newName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); + + if (oldSymbol.symbolName == newName) + return *this; + + Expression e (term->clone()); + e.term->renameSymbol (oldSymbol, newName, scope, 0); + return e; +} + +bool Expression::referencesSymbol (const Expression::Symbol& symbolToCheck, const Scope& scope) const +{ + Helpers::SymbolCheckVisitor visitor (symbolToCheck); + + try + { + term->visitAllSymbols (visitor, scope, 0); + } + catch (Helpers::EvaluationError&) + {} + + return visitor.wasFound; +} + +void Expression::findReferencedSymbols (Array& results, const Scope& scope) const +{ + try + { + Helpers::SymbolListVisitor visitor (results); + term->visitAllSymbols (visitor, scope, 0); + } + catch (Helpers::EvaluationError&) + {} +} + +String Expression::toString() const { return term->toString(); } +bool Expression::usesAnySymbols() const { return Helpers::containsAnySymbols (term); } +Expression::Type Expression::getType() const noexcept { return term->getType(); } +String Expression::getSymbolOrFunction() const { return term->getName(); } +int Expression::getNumInputs() const { return term->getNumInputs(); } +Expression Expression::getInput (int index) const { return Expression (term->getInput (index)); } + +//============================================================================== +ReferenceCountedObjectPtr Expression::Term::negated() +{ + return new Helpers::Negate (this); +} + +//============================================================================== +Expression::ParseError::ParseError (const String& message) + : description (message) +{ + DBG ("Expression::ParseError: " + message); +} + +//============================================================================== +Expression::Symbol::Symbol (const String& scopeUID_, const String& symbolName_) + : scopeUID (scopeUID_), symbolName (symbolName_) +{ +} + +bool Expression::Symbol::operator== (const Symbol& other) const noexcept +{ + return symbolName == other.symbolName && scopeUID == other.scopeUID; +} + +bool Expression::Symbol::operator!= (const Symbol& other) const noexcept +{ + return ! operator== (other); +} + +//============================================================================== +Expression::Scope::Scope() {} +Expression::Scope::~Scope() {} + +Expression Expression::Scope::getSymbolValue (const String& symbol) const +{ + if (symbol.isNotEmpty()) + throw Helpers::EvaluationError ("Unknown symbol: " + symbol); + + return Expression(); +} + +double Expression::Scope::evaluateFunction (const String& functionName, const double* parameters, int numParams) const +{ + if (numParams > 0) + { + if (functionName == "min") + { + double v = parameters[0]; + for (int i = 1; i < numParams; ++i) + v = bmin (v, parameters[i]); + + return v; + } + + if (functionName == "max") + { + double v = parameters[0]; + for (int i = 1; i < numParams; ++i) + v = bmax (v, parameters[i]); + + return v; + } + + if (numParams == 1) + { + if (functionName == "sin") return sin (parameters[0]); + if (functionName == "cos") return cos (parameters[0]); + if (functionName == "tan") return tan (parameters[0]); + if (functionName == "abs") return std::abs (parameters[0]); + } + } + + throw Helpers::EvaluationError ("Unknown function: \"" + functionName + "\""); +} + +void Expression::Scope::visitRelativeScope (const String& scopeName, Visitor&) const +{ + throw Helpers::EvaluationError ("Unknown symbol: " + scopeName); +} + +String Expression::Scope::getScopeUID() const +{ + return String::empty; +} diff --git a/modules/beast_core/maths/beast_Expression.h b/modules/beast_core/maths/beast_Expression.h new file mode 100644 index 0000000000..4147e02605 --- /dev/null +++ b/modules/beast_core/maths/beast_Expression.h @@ -0,0 +1,269 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_EXPRESSION_BEASTHEADER +#define BEAST_EXPRESSION_BEASTHEADER + +#include "../memory/beast_ReferenceCountedObject.h" +#include "../containers/beast_Array.h" +#include "../memory/beast_ScopedPointer.h" + + +//============================================================================== +/** + A class for dynamically evaluating simple numeric expressions. + + This class can parse a simple C-style string expression involving floating point + numbers, named symbols and functions. The basic arithmetic operations of +, -, *, / + are supported, as well as parentheses, and any alphanumeric identifiers are + assumed to be named symbols which will be resolved when the expression is + evaluated. + + Expressions which use identifiers and functions require a subclass of + Expression::Scope to be supplied when evaluating them, and this object + is expected to be able to resolve the symbol names and perform the functions that + are used. +*/ +class BEAST_API Expression +{ +public: + //============================================================================== + /** Creates a simple expression with a value of 0. */ + Expression(); + + /** Destructor. */ + ~Expression(); + + /** Creates a simple expression with a specified constant value. */ + explicit Expression (double constant); + + /** Creates a copy of an expression. */ + Expression (const Expression& other); + + /** Copies another expression. */ + Expression& operator= (const Expression& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + Expression (Expression&& other) noexcept; + Expression& operator= (Expression&& other) noexcept; + #endif + + /** Creates an expression by parsing a string. + If there's a syntax error in the string, this will throw a ParseError exception. + @throws ParseError + */ + explicit Expression (const String& stringToParse); + + /** Returns a string version of the expression. */ + String toString() const; + + /** Returns an expression which is an addtion operation of two existing expressions. */ + Expression operator+ (const Expression& other) const; + /** Returns an expression which is a subtraction operation of two existing expressions. */ + Expression operator- (const Expression& other) const; + /** Returns an expression which is a multiplication operation of two existing expressions. */ + Expression operator* (const Expression& other) const; + /** Returns an expression which is a division operation of two existing expressions. */ + Expression operator/ (const Expression& other) const; + /** Returns an expression which performs a negation operation on an existing expression. */ + Expression operator-() const; + + /** Returns an Expression which is an identifier reference. */ + static Expression symbol (const String& symbol); + + /** Returns an Expression which is a function call. */ + static Expression function (const String& functionName, const Array& parameters); + + /** Returns an Expression which parses a string from a character pointer, and updates the pointer + to indicate where it finished. + + The pointer is incremented so that on return, it indicates the character that follows + the end of the expression that was parsed. + + If there's a syntax error in the string, this will throw a ParseError exception. + @throws ParseError + */ + static Expression parse (String::CharPointerType& stringToParse); + + //============================================================================== + /** When evaluating an Expression object, this class is used to resolve symbols and + perform functions that the expression uses. + */ + class BEAST_API Scope + { + public: + Scope(); + virtual ~Scope(); + + /** Returns some kind of globally unique ID that identifies this scope. */ + virtual String getScopeUID() const; + + /** Returns the value of a symbol. + If the symbol is unknown, this can throw an Expression::EvaluationError exception. + The member value is set to the part of the symbol that followed the dot, if there is + one, e.g. for "foo.bar", symbol = "foo" and member = "bar". + @throws Expression::EvaluationError + */ + virtual Expression getSymbolValue (const String& symbol) const; + + /** Executes a named function. + If the function name is unknown, this can throw an Expression::EvaluationError exception. + @throws Expression::EvaluationError + */ + virtual double evaluateFunction (const String& functionName, + const double* parameters, int numParameters) const; + + /** Used as a callback by the Scope::visitRelativeScope() method. + You should never create an instance of this class yourself, it's used by the + expression evaluation code. + */ + class Visitor + { + public: + virtual ~Visitor() {} + virtual void visit (const Scope&) = 0; + }; + + /** Creates a Scope object for a named scope, and then calls a visitor + to do some kind of processing with this new scope. + + If the name is valid, this method must create a suitable (temporary) Scope + object to represent it, and must call the Visitor::visit() method with this + new scope. + */ + virtual void visitRelativeScope (const String& scopeName, Visitor& visitor) const; + }; + + /** Evaluates this expression, without using a Scope. + Without a Scope, no symbols can be used, and only basic functions such as sin, cos, tan, + min, max are available. + To find out about any errors during evaluation, use the other version of this method which + takes a String parameter. + */ + double evaluate() const; + + /** Evaluates this expression, providing a scope that should be able to evaluate any symbols + or functions that it uses. + To find out about any errors during evaluation, use the other version of this method which + takes a String parameter. + */ + double evaluate (const Scope& scope) const; + + /** Evaluates this expression, providing a scope that should be able to evaluate any symbols + or functions that it uses. + */ + double evaluate (const Scope& scope, String& evaluationError) const; + + /** Attempts to return an expression which is a copy of this one, but with a constant adjusted + to make the expression resolve to a target value. + + E.g. if the expression is "x + 10" and x is 5, then asking for a target value of 8 will return + the expression "x + 3". Obviously some expressions can't be reversed in this way, in which + case they might just be adjusted by adding a constant to the original expression. + + @throws Expression::EvaluationError + */ + Expression adjustedToGiveNewResult (double targetValue, const Scope& scope) const; + + /** Represents a symbol that is used in an Expression. */ + struct Symbol + { + Symbol (const String& scopeUID, const String& symbolName); + bool operator== (const Symbol&) const noexcept; + bool operator!= (const Symbol&) const noexcept; + + String scopeUID; /**< The unique ID of the Scope that contains this symbol. */ + String symbolName; /**< The name of the symbol. */ + }; + + /** Returns a copy of this expression in which all instances of a given symbol have been renamed. */ + Expression withRenamedSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope) const; + + /** Returns true if this expression makes use of the specified symbol. + If a suitable scope is supplied, the search will dereference and recursively check + all symbols, so that it can be determined whether this expression relies on the given + symbol at any level in its evaluation. If the scope parameter is null, this just checks + whether the expression contains any direct references to the symbol. + + @throws Expression::EvaluationError + */ + bool referencesSymbol (const Symbol& symbol, const Scope& scope) const; + + /** Returns true if this expression contains any symbols. */ + bool usesAnySymbols() const; + + /** Returns a list of all symbols that may be needed to resolve this expression in the given scope. */ + void findReferencedSymbols (Array& results, const Scope& scope) const; + + //============================================================================== + /** An exception that can be thrown by Expression::parse(). */ + class ParseError : public std::exception + { + public: + ParseError (const String& message); + + String description; + }; + + //============================================================================== + /** Expression type. + @see Expression::getType() + */ + enum Type + { + constantType, + functionType, + operatorType, + symbolType + }; + + /** Returns the type of this expression. */ + Type getType() const noexcept; + + /** If this expression is a symbol, function or operator, this returns its identifier. */ + String getSymbolOrFunction() const; + + /** Returns the number of inputs to this expression. + @see getInput + */ + int getNumInputs() const; + + /** Retrieves one of the inputs to this expression. + @see getNumInputs + */ + Expression getInput (int index) const; + +private: + //============================================================================== + class Term; + struct Helpers; + friend class Term; + friend struct Helpers; + friend class ScopedPointer; + friend class ReferenceCountedObjectPtr; + ReferenceCountedObjectPtr term; + + explicit Expression (Term*); +}; + +#endif // BEAST_EXPRESSION_BEASTHEADER diff --git a/modules/beast_core/maths/beast_MathsFunctions.h b/modules/beast_core/maths/beast_MathsFunctions.h new file mode 100644 index 0000000000..29168654d2 --- /dev/null +++ b/modules/beast_core/maths/beast_MathsFunctions.h @@ -0,0 +1,515 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MATHSFUNCTIONS_BEASTHEADER +#define BEAST_MATHSFUNCTIONS_BEASTHEADER + +//============================================================================== +/* + This file sets up some handy mathematical typdefs and functions. +*/ + +//============================================================================== +// Definitions for the int8, int16, int32, int64 and pointer_sized_int types. + +/** A platform-independent 8-bit signed integer type. */ +typedef signed char int8; +/** A platform-independent 8-bit unsigned integer type. */ +typedef unsigned char uint8; +/** A platform-independent 16-bit signed integer type. */ +typedef signed short int16; +/** A platform-independent 16-bit unsigned integer type. */ +typedef unsigned short uint16; +/** A platform-independent 32-bit signed integer type. */ +typedef signed int int32; +/** A platform-independent 32-bit unsigned integer type. */ +typedef unsigned int uint32; + +#if BEAST_MSVC + /** A platform-independent 64-bit integer type. */ + typedef __int64 int64; + /** A platform-independent 64-bit unsigned integer type. */ + typedef unsigned __int64 uint64; + /** A platform-independent macro for writing 64-bit literals, needed because + different compilers have different syntaxes for this. + + E.g. writing literal64bit (0x1000000000) will translate to 0x1000000000LL for + GCC, or 0x1000000000 for MSVC. + */ + #define literal64bit(longLiteral) ((__int64) longLiteral) +#else + /** A platform-independent 64-bit integer type. */ + typedef long long int64; + /** A platform-independent 64-bit unsigned integer type. */ + typedef unsigned long long uint64; + /** A platform-independent macro for writing 64-bit literals, needed because + different compilers have different syntaxes for this. + + E.g. writing literal64bit (0x1000000000) will translate to 0x1000000000LL for + GCC, or 0x1000000000 for MSVC. + */ + #define literal64bit(longLiteral) (longLiteral##LL) +#endif + + +#if BEAST_64BIT + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef int64 pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef uint64 pointer_sized_uint; +#elif BEAST_MSVC + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef _W64 int pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef _W64 unsigned int pointer_sized_uint; +#else + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef int pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef unsigned int pointer_sized_uint; +#endif + +#if BEAST_MSVC + typedef pointer_sized_int ssize_t; +#endif + +//============================================================================== +// Some indispensible min/max functions + +/** Returns the larger of two values. */ +template +inline Type bmax (const Type a, const Type b) { return (a < b) ? b : a; } + +/** Returns the larger of three values. */ +template +inline Type bmax (const Type a, const Type b, const Type c) { return (a < b) ? ((b < c) ? c : b) : ((a < c) ? c : a); } + +/** Returns the larger of four values. */ +template +inline Type bmax (const Type a, const Type b, const Type c, const Type d) { return bmax (a, bmax (b, c, d)); } + +/** Returns the smaller of two values. */ +template +inline Type bmin (const Type a, const Type b) { return (b < a) ? b : a; } + +/** Returns the smaller of three values. */ +template +inline Type bmin (const Type a, const Type b, const Type c) { return (b < a) ? ((c < b) ? c : b) : ((c < a) ? c : a); } + +/** Returns the smaller of four values. */ +template +inline Type bmin (const Type a, const Type b, const Type c, const Type d) { return bmin (a, bmin (b, c, d)); } + +/** Scans an array of values, returning the minimum value that it contains. */ +template +const Type findMinimum (const Type* data, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*data++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *data++; + if (v < result) result = v; + } + + return result; +} + +/** Scans an array of values, returning the maximum value that it contains. */ +template +const Type findMaximum (const Type* values, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*values++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + if (result < v) result = v; + } + + return result; +} + +/** Scans an array of values, returning the minimum and maximum values that it contains. */ +template +void findMinAndMax (const Type* values, int numValues, Type& lowest, Type& highest) +{ + if (numValues <= 0) + { + lowest = Type(); + highest = Type(); + } + else + { + Type mn (*values++); + Type mx (mn); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + + if (mx < v) mx = v; + if (v < mn) mn = v; + } + + lowest = mn; + highest = mx; + } +} + + +//============================================================================== +/** Constrains a value to keep it within a given range. + + This will check that the specified value lies between the lower and upper bounds + specified, and if not, will return the nearest value that would be in-range. Effectively, + it's like calling bmax (lowerLimit, bmin (upperLimit, value)). + + Note that it expects that lowerLimit <= upperLimit. If this isn't true, + the results will be unpredictable. + + @param lowerLimit the minimum value to return + @param upperLimit the maximum value to return + @param valueToConstrain the value to try to return + @returns the closest value to valueToConstrain which lies between lowerLimit + and upperLimit (inclusive) + @see jlimit0To, bmin, bmax +*/ +template +inline Type blimit (const Type lowerLimit, + const Type upperLimit, + const Type valueToConstrain) noexcept +{ + bassert (lowerLimit <= upperLimit); // if these are in the wrong order, results are unpredictable.. + + return (valueToConstrain < lowerLimit) ? lowerLimit + : ((upperLimit < valueToConstrain) ? upperLimit + : valueToConstrain); +} + +/** Returns true if a value is at least zero, and also below a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest < upperLimit + @endcode +*/ +template +inline bool isPositiveAndBelow (Type valueToTest, Type upperLimit) noexcept +{ + bassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest < upperLimit; +} + +template <> +inline bool isPositiveAndBelow (const int valueToTest, const int upperLimit) noexcept +{ + bassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) < static_cast (upperLimit); +} + +/** Returns true if a value is at least zero, and also less than or equal to a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest <= upperLimit + @endcode +*/ +template +inline bool isPositiveAndNotGreaterThan (Type valueToTest, Type upperLimit) noexcept +{ + bassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest <= upperLimit; +} + +template <> +inline bool isPositiveAndNotGreaterThan (const int valueToTest, const int upperLimit) noexcept +{ + bassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) <= static_cast (upperLimit); +} + +//============================================================================== +/** Handy function to swap two values. */ +template +inline void swapVariables (Type& variable1, Type& variable2) +{ + std::swap (variable1, variable2); +} + +/** Handy function for getting the number of elements in a simple const C array. + E.g. + @code + static int myArray[] = { 1, 2, 3 }; + + int numElements = numElementsInArray (myArray) // returns 3 + @endcode +*/ +template +inline int numElementsInArray (Type (&array)[N]) +{ + (void) array; // (required to avoid a spurious warning in MS compilers) + (void) sizeof (0[array]); // This line should cause an error if you pass an object with a user-defined subscript operator + return N; +} + +//============================================================================== +// Some useful maths functions that aren't always present with all compilers and build settings. + +/** Using beast_hypot is easier than dealing with the different types of hypot function + that are provided by the various platforms and compilers. */ +template +inline Type beast_hypot (Type a, Type b) noexcept +{ + #if BEAST_MSVC + return static_cast (_hypot (a, b)); + #else + return static_cast (hypot (a, b)); + #endif +} + +/** 64-bit abs function. */ +inline int64 abs64 (const int64 n) noexcept +{ + return (n >= 0) ? n : -n; +} + +//============================================================================== +/** A predefined value for Pi, at double-precision. + @see float_Pi +*/ +const double double_Pi = 3.1415926535897932384626433832795; + +/** A predefined value for Pi, at single-precision. + @see double_Pi +*/ +const float float_Pi = 3.14159265358979323846f; + + +//============================================================================== +/** The isfinite() method seems to vary between platforms, so this is a + platform-independent function for it. +*/ +template +inline bool beast_isfinite (FloatingPointType value) +{ + #if BEAST_WINDOWS + return _finite (value); + #elif BEAST_ANDROID + return isfinite (value); + #else + return std::isfinite (value); + #endif +} + +//============================================================================== +#if BEAST_MSVC + #pragma optimize ("t", off) + #ifndef __INTEL_COMPILER + #pragma float_control (precise, on, push) + #endif +#endif + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a float to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. +*/ +template +inline int roundToInt (const FloatType value) noexcept +{ + #ifdef __INTEL_COMPILER + #pragma float_control (precise, on, push) + #endif + + union { int asInt[2]; double asDouble; } n; + n.asDouble = ((double) value) + 6755399441055744.0; + + #if BEAST_BIG_ENDIAN + return n.asInt [1]; + #else + return n.asInt [0]; + #endif +} + +#if BEAST_MSVC + #ifndef __INTEL_COMPILER + #pragma float_control (pop) + #endif + #pragma optimize ("", on) // resets optimisations to the project defaults +#endif + +/** Fast floating-point-to-integer conversion. + + This is a slightly slower and slightly more accurate version of roundDoubleToInt(). It works + fine for values above zero, but negative numbers are rounded the wrong way. +*/ +inline int roundToIntAccurate (const double value) noexcept +{ + #ifdef __INTEL_COMPILER + #pragma float_control (pop) + #endif + + return roundToInt (value + 1.5e-8); +} + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a double to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. For a more accurate conversion, + see roundDoubleToIntAccurate(). +*/ +inline int roundDoubleToInt (const double value) noexcept +{ + return roundToInt (value); +} + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a float to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. +*/ +inline int roundFloatToInt (const float value) noexcept +{ + return roundToInt (value); +} + +//============================================================================== +/** Returns true if the specified integer is a power-of-two. +*/ +template +bool isPowerOfTwo (IntegerType value) +{ + return (value & (value - 1)) == 0; +} + +/** Returns the smallest power-of-two which is equal to or greater than the given integer. +*/ +inline int nextPowerOfTwo (int n) noexcept +{ + --n; + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return n + 1; +} + +/** Performs a modulo operation, but can cope with the dividend being negative. + The divisor must be greater than zero. +*/ +template +IntegerType negativeAwareModulo (IntegerType dividend, const IntegerType divisor) noexcept +{ + bassert (divisor > 0); + dividend %= divisor; + return (dividend < 0) ? (dividend + divisor) : dividend; +} + +//============================================================================== +#if (BEAST_INTEL && BEAST_32BIT) || defined (DOXYGEN) + /** This macro can be applied to a float variable to check whether it contains a denormalised + value, and to normalise it if necessary. + On CPUs that aren't vulnerable to denormalisation problems, this will have no effect. + */ + #define BEAST_UNDENORMALISE(x) x += 1.0f; x -= 1.0f; +#else + #define BEAST_UNDENORMALISE(x) +#endif + +//============================================================================== +/** This namespace contains a few template classes for helping work out class type variations. +*/ +namespace TypeHelpers +{ + #if BEAST_VC8_OR_EARLIER + #define PARAMETER_TYPE(type) const type& + #else + /** The ParameterType struct is used to find the best type to use when passing some kind + of object as a parameter. + + Of course, this is only likely to be useful in certain esoteric template situations. + + Because "typename TypeHelpers::ParameterType::type" is a bit of a mouthful, there's + a PARAMETER_TYPE(SomeClass) macro that you can use to get the same effect. + + E.g. "myFunction (PARAMETER_TYPE (int), PARAMETER_TYPE (MyObject))" + would evaluate to "myfunction (int, const MyObject&)", keeping any primitive types as + pass-by-value, but passing objects as a const reference, to avoid copying. + */ + template struct ParameterType { typedef const Type& type; }; + + #if ! DOXYGEN + template struct ParameterType { typedef Type& type; }; + template struct ParameterType { typedef Type* type; }; + template <> struct ParameterType { typedef char type; }; + template <> struct ParameterType { typedef unsigned char type; }; + template <> struct ParameterType { typedef short type; }; + template <> struct ParameterType { typedef unsigned short type; }; + template <> struct ParameterType { typedef int type; }; + template <> struct ParameterType { typedef unsigned int type; }; + template <> struct ParameterType { typedef long type; }; + template <> struct ParameterType { typedef unsigned long type; }; + template <> struct ParameterType { typedef int64 type; }; + template <> struct ParameterType { typedef uint64 type; }; + template <> struct ParameterType { typedef bool type; }; + template <> struct ParameterType { typedef float type; }; + template <> struct ParameterType { typedef double type; }; + #endif + + /** A helpful macro to simplify the use of the ParameterType template. + @see ParameterType + */ + #define PARAMETER_TYPE(a) typename TypeHelpers::ParameterType::type + #endif + + + /** These templates are designed to take a type, and if it's a double, they return a double + type; for anything else, they return a float type. + */ + template struct SmallestFloatType { typedef float type; }; + template <> struct SmallestFloatType { typedef double type; }; +} + + +//============================================================================== + +#endif // BEAST_MATHSFUNCTIONS_BEASTHEADER diff --git a/modules/beast_core/maths/beast_Random.cpp b/modules/beast_core/maths/beast_Random.cpp new file mode 100644 index 0000000000..aec64a81bb --- /dev/null +++ b/modules/beast_core/maths/beast_Random.cpp @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +Random::Random (const int64 seedValue) noexcept + : seed (seedValue) +{ +} + +Random::Random() + : seed (1) +{ + setSeedRandomly(); +} + +Random::~Random() noexcept +{ +} + +void Random::setSeed (const int64 newSeed) noexcept +{ + seed = newSeed; +} + +void Random::combineSeed (const int64 seedValue) noexcept +{ + seed ^= nextInt64() ^ seedValue; +} + +void Random::setSeedRandomly() +{ + static int64 globalSeed = 0; + + combineSeed (globalSeed ^ (int64) (pointer_sized_int) this); + combineSeed (Time::getMillisecondCounter()); + combineSeed (Time::getHighResolutionTicks()); + combineSeed (Time::getHighResolutionTicksPerSecond()); + combineSeed (Time::currentTimeMillis()); + globalSeed ^= seed; +} + +Random& Random::getSystemRandom() noexcept +{ + static Random sysRand; + return sysRand; +} + +//============================================================================== +int Random::nextInt() noexcept +{ + seed = (seed * literal64bit (0x5deece66d) + 11) & literal64bit (0xffffffffffff); + + return (int) (seed >> 16); +} + +int Random::nextInt (const int maxValue) noexcept +{ + bassert (maxValue > 0); + return (int) ((((unsigned int) nextInt()) * (uint64) maxValue) >> 32); +} + +int64 Random::nextInt64() noexcept +{ + return (((int64) nextInt()) << 32) | (int64) (uint64) (uint32) nextInt(); +} + +bool Random::nextBool() noexcept +{ + return (nextInt() & 0x40000000) != 0; +} + +float Random::nextFloat() noexcept +{ + return static_cast (nextInt()) / (float) 0xffffffff; +} + +double Random::nextDouble() noexcept +{ + return static_cast (nextInt()) / (double) 0xffffffff; +} + +BigInteger Random::nextLargeNumber (const BigInteger& maximumValue) +{ + BigInteger n; + + do + { + fillBitsRandomly (n, 0, maximumValue.getHighestBit() + 1); + } + while (n >= maximumValue); + + return n; +} + +void Random::fillBitsRandomly (BigInteger& arrayToChange, int startBit, int numBits) +{ + arrayToChange.setBit (startBit + numBits - 1, true); // to force the array to pre-allocate space + + while ((startBit & 31) != 0 && numBits > 0) + { + arrayToChange.setBit (startBit++, nextBool()); + --numBits; + } + + while (numBits >= 32) + { + arrayToChange.setBitRangeAsInt (startBit, 32, (unsigned int) nextInt()); + startBit += 32; + numBits -= 32; + } + + while (--numBits >= 0) + arrayToChange.setBit (startBit + numBits, nextBool()); +} + +//============================================================================== +#if BEAST_UNIT_TESTS + +class RandomTests : public UnitTest +{ +public: + RandomTests() : UnitTest ("Random") {} + + void runTest() + { + beginTest ("Random"); + + for (int j = 10; --j >= 0;) + { + Random r; + r.setSeedRandomly(); + + for (int i = 20; --i >= 0;) + { + expect (r.nextDouble() >= 0.0 && r.nextDouble() < 1.0); + expect (r.nextFloat() >= 0.0f && r.nextFloat() < 1.0f); + expect (r.nextInt (5) >= 0 && r.nextInt (5) < 5); + expect (r.nextInt (1) == 0); + + int n = r.nextInt (50) + 1; + expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + + n = r.nextInt (0x7ffffffe) + 1; + expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + } + } + } +}; + +static RandomTests randomTests; + +#endif diff --git a/modules/beast_core/maths/beast_Random.h b/modules/beast_core/maths/beast_Random.h new file mode 100644 index 0000000000..cf9ce61613 --- /dev/null +++ b/modules/beast_core/maths/beast_Random.h @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_RANDOM_BEASTHEADER +#define BEAST_RANDOM_BEASTHEADER + +#include "beast_BigInteger.h" + + +//============================================================================== +/** + A random number generator. + + You can create a Random object and use it to generate a sequence of random numbers. +*/ +class BEAST_API Random +{ +public: + //============================================================================== + /** Creates a Random object based on a seed value. + + For a given seed value, the subsequent numbers generated by this object + will be predictable, so a good idea is to set this value based + on the time, e.g. + + new Random (Time::currentTimeMillis()) + */ + explicit Random (int64 seedValue) noexcept; + + /** Creates a Random object using a random seed value. + Internally, this calls setSeedRandomly() to randomise the seed. + */ + Random(); + + /** Destructor. */ + ~Random() noexcept; + + /** Returns the next random 32 bit integer. + + @returns a random integer from the full range 0x80000000 to 0x7fffffff + */ + int nextInt() noexcept; + + /** Returns the next random number, limited to a given range. + The maxValue parameter may not be negative, or zero. + @returns a random integer between 0 (inclusive) and maxValue (exclusive). + */ + int nextInt (int maxValue) noexcept; + + /** Returns the next 64-bit random number. + + @returns a random integer from the full range 0x8000000000000000 to 0x7fffffffffffffff + */ + int64 nextInt64() noexcept; + + /** Returns the next random floating-point number. + + @returns a random value in the range 0 to 1.0 + */ + float nextFloat() noexcept; + + /** Returns the next random floating-point number. + + @returns a random value in the range 0 to 1.0 + */ + double nextDouble() noexcept; + + /** Returns the next random boolean value. + */ + bool nextBool() noexcept; + + /** Returns a BigInteger containing a random number. + + @returns a random value in the range 0 to (maximumValue - 1). + */ + BigInteger nextLargeNumber (const BigInteger& maximumValue); + + /** Sets a range of bits in a BigInteger to random values. */ + void fillBitsRandomly (BigInteger& arrayToChange, int startBit, int numBits); + + //============================================================================== + /** Resets this Random object to a given seed value. */ + void setSeed (int64 newSeed) noexcept; + + /** Merges this object's seed with another value. + This sets the seed to be a value created by combining the current seed and this + new value. + */ + void combineSeed (int64 seedValue) noexcept; + + /** Reseeds this generator using a value generated from various semi-random system + properties like the current time, etc. + + Because this function convolves the time with the last seed value, calling + it repeatedly will increase the randomness of the final result. + */ + void setSeedRandomly(); + + /** The overhead of creating a new Random object is fairly small, but if you want to avoid + it, you can call this method to get a global shared Random object. + + It's not thread-safe though, so threads should use their own Random object, otherwise + you run the risk of your random numbers becoming.. erm.. randomly corrupted.. + */ + static Random& getSystemRandom() noexcept; + +private: + //============================================================================== + int64 seed; + + BEAST_LEAK_DETECTOR (Random) +}; + + +#endif // BEAST_RANDOM_BEASTHEADER diff --git a/modules/beast_core/maths/beast_Range.h b/modules/beast_core/maths/beast_Range.h new file mode 100644 index 0000000000..dc3a1f2179 --- /dev/null +++ b/modules/beast_core/maths/beast_Range.h @@ -0,0 +1,259 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_RANGE_BEASTHEADER +#define BEAST_RANGE_BEASTHEADER + + +//============================================================================== +/** A general-purpose range object, that simply represents any linear range with + a start and end point. + + The templated parameter is expected to be a primitive integer or floating point + type, though class types could also be used if they behave in a number-like way. +*/ +template +class Range +{ +public: + //============================================================================== + /** Constructs an empty range. */ + Range() noexcept : start(), end() + { + } + + /** Constructs a range with given start and end values. */ + Range (const ValueType startValue, const ValueType endValue) noexcept + : start (startValue), end (bmax (startValue, endValue)) + { + } + + /** Constructs a copy of another range. */ + Range (const Range& other) noexcept + : start (other.start), end (other.end) + { + } + + /** Copies another range object. */ + Range& operator= (Range other) noexcept + { + start = other.start; + end = other.end; + return *this; + } + + /** Returns the range that lies between two positions (in either order). */ + static Range between (const ValueType position1, const ValueType position2) noexcept + { + return position1 < position2 ? Range (position1, position2) + : Range (position2, position1); + } + + /** Returns a range with the specified start position and a length of zero. */ + static Range emptyRange (const ValueType start) noexcept + { + return Range (start, start); + } + + //============================================================================== + /** Returns the start of the range. */ + inline ValueType getStart() const noexcept { return start; } + + /** Returns the length of the range. */ + inline ValueType getLength() const noexcept { return end - start; } + + /** Returns the end of the range. */ + inline ValueType getEnd() const noexcept { return end; } + + /** Returns true if the range has a length of zero. */ + inline bool isEmpty() const noexcept { return start == end; } + + //============================================================================== + /** Changes the start position of the range, leaving the end position unchanged. + If the new start position is higher than the current end of the range, the end point + will be pushed along to equal it, leaving an empty range at the new position. + */ + void setStart (const ValueType newStart) noexcept + { + start = newStart; + if (end < newStart) + end = newStart; + } + + /** Returns a range with the same end as this one, but a different start. + If the new start position is higher than the current end of the range, the end point + will be pushed along to equal it, returning an empty range at the new position. + */ + Range withStart (const ValueType newStart) const noexcept + { + return Range (newStart, bmax (newStart, end)); + } + + /** Returns a range with the same length as this one, but moved to have the given start position. */ + Range movedToStartAt (const ValueType newStart) const noexcept + { + return Range (newStart, end + (newStart - start)); + } + + /** Changes the end position of the range, leaving the start unchanged. + If the new end position is below the current start of the range, the start point + will be pushed back to equal the new end point. + */ + void setEnd (const ValueType newEnd) noexcept + { + end = newEnd; + if (newEnd < start) + start = newEnd; + } + + /** Returns a range with the same start position as this one, but a different end. + If the new end position is below the current start of the range, the start point + will be pushed back to equal the new end point. + */ + Range withEnd (const ValueType newEnd) const noexcept + { + return Range (bmin (start, newEnd), newEnd); + } + + /** Returns a range with the same length as this one, but moved to have the given end position. */ + Range movedToEndAt (const ValueType newEnd) const noexcept + { + return Range (start + (newEnd - end), newEnd); + } + + /** Changes the length of the range. + Lengths less than zero are treated as zero. + */ + void setLength (const ValueType newLength) noexcept + { + end = start + bmax (ValueType(), newLength); + } + + /** Returns a range with the same start as this one, but a different length. + Lengths less than zero are treated as zero. + */ + Range withLength (const ValueType newLength) const noexcept + { + return Range (start, start + newLength); + } + + //============================================================================== + /** Adds an amount to the start and end of the range. */ + inline Range operator+= (const ValueType amountToAdd) noexcept + { + start += amountToAdd; + end += amountToAdd; + return *this; + } + + /** Subtracts an amount from the start and end of the range. */ + inline Range operator-= (const ValueType amountToSubtract) noexcept + { + start -= amountToSubtract; + end -= amountToSubtract; + return *this; + } + + /** Returns a range that is equal to this one with an amount added to its + start and end. + */ + Range operator+ (const ValueType amountToAdd) const noexcept + { + return Range (start + amountToAdd, end + amountToAdd); + } + + /** Returns a range that is equal to this one with the specified amount + subtracted from its start and end. */ + Range operator- (const ValueType amountToSubtract) const noexcept + { + return Range (start - amountToSubtract, end - amountToSubtract); + } + + bool operator== (Range other) const noexcept { return start == other.start && end == other.end; } + bool operator!= (Range other) const noexcept { return start != other.start || end != other.end; } + + //============================================================================== + /** Returns true if the given position lies inside this range. */ + bool contains (const ValueType position) const noexcept + { + return start <= position && position < end; + } + + /** Returns the nearest value to the one supplied, which lies within the range. */ + ValueType clipValue (const ValueType value) const noexcept + { + return blimit (start, end, value); + } + + /** Returns true if the given range lies entirely inside this range. */ + bool contains (Range other) const noexcept + { + return start <= other.start && end >= other.end; + } + + /** Returns true if the given range intersects this one. */ + bool intersects (Range other) const noexcept + { + return other.start < end && start < other.end; + } + + /** Returns the range that is the intersection of the two ranges, or an empty range + with an undefined start position if they don't overlap. */ + Range getIntersectionWith (Range other) const noexcept + { + return Range (bmax (start, other.start), + bmin (end, other.end)); + } + + /** Returns the smallest range that contains both this one and the other one. */ + Range getUnionWith (Range other) const noexcept + { + return Range (bmin (start, other.start), + bmax (end, other.end)); + } + + /** Returns a given range, after moving it forwards or backwards to fit it + within this range. + + If the supplied range has a greater length than this one, the return value + will be this range. + + Otherwise, if the supplied range is smaller than this one, the return value + will be the new range, shifted forwards or backwards so that it doesn't extend + beyond this one, but keeping its original length. + */ + Range constrainRange (Range rangeToConstrain) const noexcept + { + const ValueType otherLen = rangeToConstrain.getLength(); + return getLength() <= otherLen + ? *this + : rangeToConstrain.movedToStartAt (blimit (start, end - otherLen, rangeToConstrain.getStart())); + } + +private: + //============================================================================== + ValueType start, end; +}; + + +#endif // BEAST_RANGE_BEASTHEADER diff --git a/modules/beast_core/memory/beast_Atomic.h b/modules/beast_core/memory/beast_Atomic.h new file mode 100644 index 0000000000..43eef53f9a --- /dev/null +++ b/modules/beast_core/memory/beast_Atomic.h @@ -0,0 +1,388 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ATOMIC_BEASTHEADER +#define BEAST_ATOMIC_BEASTHEADER + + +//============================================================================== +/** + Simple class to hold a primitive value and perform atomic operations on it. + + The type used must be a 32 or 64 bit primitive, like an int, pointer, etc. + There are methods to perform most of the basic atomic operations. +*/ +template +class Atomic +{ +public: + /** Creates a new value, initialised to zero. */ + inline Atomic() noexcept + : value (0) + { + } + + /** Creates a new value, with a given initial value. */ + inline Atomic (const Type initialValue) noexcept + : value (initialValue) + { + } + + /** Copies another value (atomically). */ + inline Atomic (const Atomic& other) noexcept + : value (other.get()) + { + } + + /** Destructor. */ + inline ~Atomic() noexcept + { + // This class can only be used for types which are 32 or 64 bits in size. + static_bassert (sizeof (Type) == 4 || sizeof (Type) == 8); + } + + /** Atomically reads and returns the current value. */ + Type get() const noexcept; + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Atomic& other) noexcept { exchange (other.get()); return *this; } + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Type newValue) noexcept { exchange (newValue); return *this; } + + /** Atomically sets the current value. */ + void set (Type newValue) noexcept { exchange (newValue); } + + /** Atomically sets the current value, returning the value that was replaced. */ + Type exchange (Type value) noexcept; + + /** Atomically adds a number to this value, returning the new value. */ + Type operator+= (Type amountToAdd) noexcept; + + /** Atomically subtracts a number from this value, returning the new value. */ + Type operator-= (Type amountToSubtract) noexcept; + + /** Atomically increments this value, returning the new value. */ + Type operator++() noexcept; + + /** Atomically decrements this value, returning the new value. */ + Type operator--() noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + bool compareAndSetBool (Type newValue, Type valueToCompare) + { + if (get() == valueToCompare) + { + set (newValue); + return true; + } + + return false; + } + @endcode + + @returns true if the comparison was true and the value was replaced; false if + the comparison failed and the value was left unchanged. + @see compareAndSetValue + */ + bool compareAndSetBool (Type newValue, Type valueToCompare) noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + Type compareAndSetValue (Type newValue, Type valueToCompare) + { + Type oldValue = get(); + if (oldValue == valueToCompare) + set (newValue); + + return oldValue; + } + @endcode + + @returns the old value before it was changed. + @see compareAndSetBool + */ + Type compareAndSetValue (Type newValue, Type valueToCompare) noexcept; + + /** Implements a memory read/write barrier. */ + static void memoryBarrier() noexcept; + + //============================================================================== + #if BEAST_64BIT + BEAST_ALIGN (8) + #else + BEAST_ALIGN (4) + #endif + + /** The raw value that this class operates on. + This is exposed publically in case you need to manipulate it directly + for performance reasons. + */ + volatile Type value; + +private: + static inline Type castFrom32Bit (int32 value) noexcept { return *(Type*) &value; } + static inline Type castFrom64Bit (int64 value) noexcept { return *(Type*) &value; } + static inline int32 castTo32Bit (Type value) noexcept { return *(int32*) &value; } + static inline int64 castTo64Bit (Type value) noexcept { return *(int64*) &value; } + + Type operator++ (int); // better to just use pre-increment with atomics.. + Type operator-- (int); + + /** This templated negate function will negate pointers as well as integers */ + template + inline ValueType negateValue (ValueType n) noexcept + { + return sizeof (ValueType) == 1 ? (ValueType) -(signed char) n + : (sizeof (ValueType) == 2 ? (ValueType) -(short) n + : (sizeof (ValueType) == 4 ? (ValueType) -(int) n + : ((ValueType) -(int64) n))); + } + + /** This templated negate function will negate pointers as well as integers */ + template + inline PointerType* negateValue (PointerType* n) noexcept + { + return reinterpret_cast (-reinterpret_cast (n)); + } +}; + + +//============================================================================== +/* + The following code is in the header so that the atomics can be inlined where possible... +*/ +#if BEAST_IOS || (BEAST_MAC && (BEAST_PPC || BEAST_CLANG || __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2))) + #define BEAST_ATOMICS_MAC 1 // Older OSX builds using gcc4.1 or earlier + + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + #define BEAST_MAC_ATOMICS_VOLATILE + #else + #define BEAST_MAC_ATOMICS_VOLATILE volatile + #endif + + #if BEAST_PPC || BEAST_IOS + // None of these atomics are available for PPC or for iOS 3.1 or earlier!! + template static Type OSAtomicAdd64Barrier (Type b, BEAST_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return *a += b; } + template static Type OSAtomicIncrement64Barrier (BEAST_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return ++*a; } + template static Type OSAtomicDecrement64Barrier (BEAST_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return --*a; } + template static bool OSAtomicCompareAndSwap64Barrier (Type old, Type newValue, BEAST_MAC_ATOMICS_VOLATILE Type* value) noexcept + { jassertfalse; if (old == *value) { *value = newValue; return true; } return false; } + #define BEAST_64BIT_ATOMICS_UNAVAILABLE 1 + #endif + +//============================================================================== +#elif BEAST_GCC + #define BEAST_ATOMICS_GCC 1 // GCC with intrinsics + + #if BEAST_IOS || BEAST_ANDROID // (64-bit ops will compile but not link on these mobile OSes) + #define BEAST_64BIT_ATOMICS_UNAVAILABLE 1 + #endif + +//============================================================================== +#else + #define BEAST_ATOMICS_WINDOWS 1 // Windows with intrinsics + + #if BEAST_USE_INTRINSICS + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchange, _InterlockedIncrement, _InterlockedDecrement, _InterlockedCompareExchange, \ + _InterlockedCompareExchange64, _InterlockedExchangeAdd, _ReadWriteBarrier) + #endif + #define beast_InterlockedExchange(a, b) _InterlockedExchange(a, b) + #define beast_InterlockedIncrement(a) _InterlockedIncrement(a) + #define beast_InterlockedDecrement(a) _InterlockedDecrement(a) + #define beast_InterlockedExchangeAdd(a, b) _InterlockedExchangeAdd(a, b) + #define beast_InterlockedCompareExchange(a, b, c) _InterlockedCompareExchange(a, b, c) + #define beast_InterlockedCompareExchange64(a, b, c) _InterlockedCompareExchange64(a, b, c) + #define beast_MemoryBarrier _ReadWriteBarrier + #else + long beast_InterlockedExchange (volatile long* a, long b) noexcept; + long beast_InterlockedIncrement (volatile long* a) noexcept; + long beast_InterlockedDecrement (volatile long* a) noexcept; + long beast_InterlockedExchangeAdd (volatile long* a, long b) noexcept; + long beast_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept; + __int64 beast_InterlockedCompareExchange64 (volatile __int64* a, __int64 b, __int64 c) noexcept; + inline void beast_MemoryBarrier() noexcept { long x = 0; beast_InterlockedIncrement (&x); } + #endif + + #if BEAST_64BIT + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchangeAdd64, _InterlockedExchange64, _InterlockedIncrement64, _InterlockedDecrement64) + #endif + #define beast_InterlockedExchangeAdd64(a, b) _InterlockedExchangeAdd64(a, b) + #define beast_InterlockedExchange64(a, b) _InterlockedExchange64(a, b) + #define beast_InterlockedIncrement64(a) _InterlockedIncrement64(a) + #define beast_InterlockedDecrement64(a) _InterlockedDecrement64(a) + #else + // None of these atomics are available in a 32-bit Windows build!! + template static Type beast_InterlockedExchangeAdd64 (volatile Type* a, Type b) noexcept { jassertfalse; Type old = *a; *a += b; return old; } + template static Type beast_InterlockedExchange64 (volatile Type* a, Type b) noexcept { jassertfalse; Type old = *a; *a = b; return old; } + template static Type beast_InterlockedIncrement64 (volatile Type* a) noexcept { jassertfalse; return ++*a; } + template static Type beast_InterlockedDecrement64 (volatile Type* a) noexcept { jassertfalse; return --*a; } + #define BEAST_64BIT_ATOMICS_UNAVAILABLE 1 + #endif +#endif + +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4311) // (truncation warning) +#endif + +//============================================================================== +template +inline Type Atomic::get() const noexcept +{ + #if BEAST_ATOMICS_MAC + return sizeof (Type) == 4 ? castFrom32Bit ((int32) OSAtomicAdd32Barrier ((int32_t) 0, (BEAST_MAC_ATOMICS_VOLATILE int32_t*) &value)) + : castFrom64Bit ((int64) OSAtomicAdd64Barrier ((int64_t) 0, (BEAST_MAC_ATOMICS_VOLATILE int64_t*) &value)); + #elif BEAST_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) beast_InterlockedExchangeAdd ((volatile long*) &value, (long) 0)) + : castFrom64Bit ((int64) beast_InterlockedExchangeAdd64 ((volatile __int64*) &value, (__int64) 0)); + #elif BEAST_ATOMICS_GCC + return sizeof (Type) == 4 ? castFrom32Bit ((int32) __sync_add_and_fetch ((volatile int32*) &value, 0)) + : castFrom64Bit ((int64) __sync_add_and_fetch ((volatile int64*) &value, 0)); + #endif +} + +template +inline Type Atomic::exchange (const Type newValue) noexcept +{ + #if BEAST_ATOMICS_MAC || BEAST_ATOMICS_GCC + Type currentVal = value; + while (! compareAndSetBool (newValue, currentVal)) { currentVal = value; } + return currentVal; + #elif BEAST_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) beast_InterlockedExchange ((volatile long*) &value, (long) castTo32Bit (newValue))) + : castFrom64Bit ((int64) beast_InterlockedExchange64 ((volatile __int64*) &value, (__int64) castTo64Bit (newValue))); + #endif +} + +template +inline Type Atomic::operator+= (const Type amountToAdd) noexcept +{ + #if BEAST_ATOMICS_MAC + return sizeof (Type) == 4 ? (Type) OSAtomicAdd32Barrier ((int32_t) castTo32Bit (amountToAdd), (BEAST_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicAdd64Barrier ((int64_t) amountToAdd, (BEAST_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif BEAST_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) (beast_InterlockedExchangeAdd ((volatile long*) &value, (long) amountToAdd) + (long) amountToAdd) + : (Type) (beast_InterlockedExchangeAdd64 ((volatile __int64*) &value, (__int64) amountToAdd) + (__int64) amountToAdd); + #elif BEAST_ATOMICS_GCC + return (Type) __sync_add_and_fetch (&value, amountToAdd); + #endif +} + +template +inline Type Atomic::operator-= (const Type amountToSubtract) noexcept +{ + return operator+= (negateValue (amountToSubtract)); +} + +template +inline Type Atomic::operator++() noexcept +{ + #if BEAST_ATOMICS_MAC + return sizeof (Type) == 4 ? (Type) OSAtomicIncrement32Barrier ((BEAST_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicIncrement64Barrier ((BEAST_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif BEAST_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) beast_InterlockedIncrement ((volatile long*) &value) + : (Type) beast_InterlockedIncrement64 ((volatile __int64*) &value); + #elif BEAST_ATOMICS_GCC + return (Type) __sync_add_and_fetch (&value, 1); + #endif +} + +template +inline Type Atomic::operator--() noexcept +{ + #if BEAST_ATOMICS_MAC + return sizeof (Type) == 4 ? (Type) OSAtomicDecrement32Barrier ((BEAST_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicDecrement64Barrier ((BEAST_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif BEAST_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) beast_InterlockedDecrement ((volatile long*) &value) + : (Type) beast_InterlockedDecrement64 ((volatile __int64*) &value); + #elif BEAST_ATOMICS_GCC + return (Type) __sync_add_and_fetch (&value, -1); + #endif +} + +template +inline bool Atomic::compareAndSetBool (const Type newValue, const Type valueToCompare) noexcept +{ + #if BEAST_ATOMICS_MAC + return sizeof (Type) == 4 ? OSAtomicCompareAndSwap32Barrier ((int32_t) castTo32Bit (valueToCompare), (int32_t) castTo32Bit (newValue), (BEAST_MAC_ATOMICS_VOLATILE int32_t*) &value) + : OSAtomicCompareAndSwap64Barrier ((int64_t) castTo64Bit (valueToCompare), (int64_t) castTo64Bit (newValue), (BEAST_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif BEAST_ATOMICS_WINDOWS + return compareAndSetValue (newValue, valueToCompare) == valueToCompare; + #elif BEAST_ATOMICS_GCC + return sizeof (Type) == 4 ? __sync_bool_compare_and_swap ((volatile int32*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue)) + : __sync_bool_compare_and_swap ((volatile int64*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue)); + #endif +} + +template +inline Type Atomic::compareAndSetValue (const Type newValue, const Type valueToCompare) noexcept +{ + #if BEAST_ATOMICS_MAC + for (;;) // Annoying workaround for only having a bool CAS operation.. + { + if (compareAndSetBool (newValue, valueToCompare)) + return valueToCompare; + + const Type result = value; + if (result != valueToCompare) + return result; + } + + #elif BEAST_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) beast_InterlockedCompareExchange ((volatile long*) &value, (long) castTo32Bit (newValue), (long) castTo32Bit (valueToCompare))) + : castFrom64Bit ((int64) beast_InterlockedCompareExchange64 ((volatile __int64*) &value, (__int64) castTo64Bit (newValue), (__int64) castTo64Bit (valueToCompare))); + #elif BEAST_ATOMICS_GCC + return sizeof (Type) == 4 ? castFrom32Bit ((int32) __sync_val_compare_and_swap ((volatile int32*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue))) + : castFrom64Bit ((int64) __sync_val_compare_and_swap ((volatile int64*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue))); + #endif +} + +template +inline void Atomic::memoryBarrier() noexcept +{ + #if BEAST_ATOMICS_MAC + OSMemoryBarrier(); + #elif BEAST_ATOMICS_GCC + __sync_synchronize(); + #elif BEAST_ATOMICS_WINDOWS + beast_MemoryBarrier(); + #endif +} + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +#endif // BEAST_ATOMIC_BEASTHEADER diff --git a/modules/beast_core/memory/beast_ByteOrder.h b/modules/beast_core/memory/beast_ByteOrder.h new file mode 100644 index 0000000000..0d41fe2266 --- /dev/null +++ b/modules/beast_core/memory/beast_ByteOrder.h @@ -0,0 +1,181 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_BYTEORDER_BEASTHEADER +#define BEAST_BYTEORDER_BEASTHEADER + + +//============================================================================== +/** Contains static methods for converting the byte order between different + endiannesses. +*/ +class BEAST_API ByteOrder +{ +public: + //============================================================================== + /** Swaps the upper and lower bytes of a 16-bit integer. */ + static uint16 swap (uint16 value); + + /** Reverses the order of the 4 bytes in a 32-bit integer. */ + static uint32 swap (uint32 value); + + /** Reverses the order of the 8 bytes in a 64-bit integer. */ + static uint64 swap (uint64 value); + + //============================================================================== + /** Swaps the byte order of a 16-bit int if the CPU is big-endian */ + static uint16 swapIfBigEndian (uint16 value); + + /** Swaps the byte order of a 32-bit int if the CPU is big-endian */ + static uint32 swapIfBigEndian (uint32 value); + + /** Swaps the byte order of a 64-bit int if the CPU is big-endian */ + static uint64 swapIfBigEndian (uint64 value); + + /** Swaps the byte order of a 16-bit int if the CPU is little-endian */ + static uint16 swapIfLittleEndian (uint16 value); + + /** Swaps the byte order of a 32-bit int if the CPU is little-endian */ + static uint32 swapIfLittleEndian (uint32 value); + + /** Swaps the byte order of a 64-bit int if the CPU is little-endian */ + static uint64 swapIfLittleEndian (uint64 value); + + //============================================================================== + /** Turns 4 bytes into a little-endian integer. */ + static uint32 littleEndianInt (const void* bytes); + + /** Turns 2 bytes into a little-endian integer. */ + static uint16 littleEndianShort (const void* bytes); + + /** Turns 4 bytes into a big-endian integer. */ + static uint32 bigEndianInt (const void* bytes); + + /** Turns 2 bytes into a big-endian integer. */ + static uint16 bigEndianShort (const void* bytes); + + //============================================================================== + /** Converts 3 little-endian bytes into a signed 24-bit value (which is sign-extended to 32 bits). */ + static int littleEndian24Bit (const char* bytes); + + /** Converts 3 big-endian bytes into a signed 24-bit value (which is sign-extended to 32 bits). */ + static int bigEndian24Bit (const char* bytes); + + /** Copies a 24-bit number to 3 little-endian bytes. */ + static void littleEndian24BitToChars (int value, char* destBytes); + + /** Copies a 24-bit number to 3 big-endian bytes. */ + static void bigEndian24BitToChars (int value, char* destBytes); + + //============================================================================== + /** Returns true if the current CPU is big-endian. */ + static bool isBigEndian(); + +private: + ByteOrder(); + + BEAST_DECLARE_NON_COPYABLE (ByteOrder) +}; + + +//============================================================================== +#if BEAST_USE_INTRINSICS && ! defined (__INTEL_COMPILER) + #pragma intrinsic (_byteswap_ulong) +#endif + +inline uint16 ByteOrder::swap (uint16 n) +{ + #if BEAST_USE_INTRINSICSxxx // agh - the MS compiler has an internal error when you try to use this intrinsic! + return static_cast (_byteswap_ushort (n)); + #else + return static_cast ((n << 8) | (n >> 8)); + #endif +} + +inline uint32 ByteOrder::swap (uint32 n) +{ + #if BEAST_MAC || BEAST_IOS + return OSSwapInt32 (n); + #elif BEAST_GCC && BEAST_INTEL && ! BEAST_NO_INLINE_ASM + asm("bswap %%eax" : "=a"(n) : "a"(n)); + return n; + #elif BEAST_USE_INTRINSICS + return _byteswap_ulong (n); + #elif BEAST_MSVC && ! BEAST_NO_INLINE_ASM + __asm { + mov eax, n + bswap eax + mov n, eax + } + return n; + #elif BEAST_ANDROID + return bswap_32 (n); + #else + return (n << 24) | (n >> 24) | ((n & 0xff00) << 8) | ((n & 0xff0000) >> 8); + #endif +} + +inline uint64 ByteOrder::swap (uint64 value) +{ + #if BEAST_MAC || BEAST_IOS + return OSSwapInt64 (value); + #elif BEAST_USE_INTRINSICS + return _byteswap_uint64 (value); + #else + return (((int64) swap ((uint32) value)) << 32) | swap ((uint32) (value >> 32)); + #endif +} + +#if BEAST_LITTLE_ENDIAN + inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) { return v; } + inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) { return v; } + inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) { return v; } + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) { return swap (v); } + inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) { return swap (v); } + inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) { return swap (v); } + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) { return *static_cast (bytes); } + inline uint16 ByteOrder::littleEndianShort (const void* const bytes) { return *static_cast (bytes); } + inline uint32 ByteOrder::bigEndianInt (const void* const bytes) { return swap (*static_cast (bytes)); } + inline uint16 ByteOrder::bigEndianShort (const void* const bytes) { return swap (*static_cast (bytes)); } + inline bool ByteOrder::isBigEndian() { return false; } +#else + inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) { return swap (v); } + inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) { return swap (v); } + inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) { return swap (v); } + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) { return v; } + inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) { return v; } + inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) { return v; } + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) { return swap (*static_cast (bytes)); } + inline uint16 ByteOrder::littleEndianShort (const void* const bytes) { return swap (*static_cast (bytes)); } + inline uint32 ByteOrder::bigEndianInt (const void* const bytes) { return *static_cast (bytes); } + inline uint16 ByteOrder::bigEndianShort (const void* const bytes) { return *static_cast (bytes); } + inline bool ByteOrder::isBigEndian() { return true; } +#endif + +inline int ByteOrder::littleEndian24Bit (const char* const bytes) { return (((int) bytes[2]) << 16) | (((int) (uint8) bytes[1]) << 8) | ((int) (uint8) bytes[0]); } +inline int ByteOrder::bigEndian24Bit (const char* const bytes) { return (((int) bytes[0]) << 16) | (((int) (uint8) bytes[1]) << 8) | ((int) (uint8) bytes[2]); } +inline void ByteOrder::littleEndian24BitToChars (const int value, char* const destBytes) { destBytes[0] = (char)(value & 0xff); destBytes[1] = (char)((value >> 8) & 0xff); destBytes[2] = (char)((value >> 16) & 0xff); } +inline void ByteOrder::bigEndian24BitToChars (const int value, char* const destBytes) { destBytes[0] = (char)((value >> 16) & 0xff); destBytes[1] = (char)((value >> 8) & 0xff); destBytes[2] = (char)(value & 0xff); } + + +#endif // BEAST_BYTEORDER_BEASTHEADER diff --git a/modules/beast_core/memory/beast_HeapBlock.h b/modules/beast_core/memory/beast_HeapBlock.h new file mode 100644 index 0000000000..4badf561c2 --- /dev/null +++ b/modules/beast_core/memory/beast_HeapBlock.h @@ -0,0 +1,303 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_HEAPBLOCK_BEASTHEADER +#define BEAST_HEAPBLOCK_BEASTHEADER + +#ifndef DOXYGEN +namespace HeapBlockHelper +{ + template + struct ThrowOnFail { static void check (void*) {} }; + + template<> + struct ThrowOnFail { static void check (void* data) { if (data == nullptr) throw std::bad_alloc(); } }; +} +#endif + +//============================================================================== +/** + Very simple container class to hold a pointer to some data on the heap. + + When you need to allocate some heap storage for something, always try to use + this class instead of allocating the memory directly using malloc/free. + + A HeapBlock object can be treated in pretty much exactly the same way + as an char*, but as long as you allocate it on the stack or as a class member, + it's almost impossible for it to leak memory. + + It also makes your code much more concise and readable than doing the same thing + using direct allocations, + + E.g. instead of this: + @code + int* temp = (int*) malloc (1024 * sizeof (int)); + memcpy (temp, xyz, 1024 * sizeof (int)); + free (temp); + temp = (int*) calloc (2048 * sizeof (int)); + temp[0] = 1234; + memcpy (foobar, temp, 2048 * sizeof (int)); + free (temp); + @endcode + + ..you could just write this: + @code + HeapBlock temp (1024); + memcpy (temp, xyz, 1024 * sizeof (int)); + temp.calloc (2048); + temp[0] = 1234; + memcpy (foobar, temp, 2048 * sizeof (int)); + @endcode + + The class is extremely lightweight, containing only a pointer to the + data, and exposes malloc/realloc/calloc/free methods that do the same jobs + as their less object-oriented counterparts. Despite adding safety, you probably + won't sacrifice any performance by using this in place of normal pointers. + + The throwOnFailure template parameter can be set to true if you'd like the class + to throw a std::bad_alloc exception when an allocation fails. If this is false, + then a failed allocation will just leave the heapblock with a null pointer (assuming + that the system's malloc() function doesn't throw). + + @see Array, OwnedArray, MemoryBlock +*/ +template +class HeapBlock +{ +public: + //============================================================================== + /** Creates a HeapBlock which is initially just a null pointer. + + After creation, you can resize the array using the malloc(), calloc(), + or realloc() methods. + */ + HeapBlock() noexcept : data (nullptr) + { + } + + /** Creates a HeapBlock containing a number of elements. + + The contents of the block are undefined, as it will have been created by a + malloc call. + + If you want an array of zero values, you can use the calloc() method or the + other constructor that takes an InitialisationState parameter. + */ + explicit HeapBlock (const size_t numElements) + : data (static_cast (std::malloc (numElements * sizeof (ElementType)))) + { + throwOnAllocationFailure(); + } + + /** Creates a HeapBlock containing a number of elements. + + The initialiseToZero parameter determines whether the new memory should be cleared, + or left uninitialised. + */ + HeapBlock (const size_t numElements, const bool initialiseToZero) + : data (static_cast (initialiseToZero + ? std::calloc (numElements, sizeof (ElementType)) + : std::malloc (numElements * sizeof (ElementType)))) + { + throwOnAllocationFailure(); + } + + /** Destructor. + This will free the data, if any has been allocated. + */ + ~HeapBlock() + { + std::free (data); + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + HeapBlock (HeapBlock&& other) noexcept + : data (other.data) + { + other.data = nullptr; + } + + HeapBlock& operator= (HeapBlock&& other) noexcept + { + std::swap (data, other.data); + return *this; + } + #endif + + //============================================================================== + /** Returns a raw pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator ElementType*() const noexcept { return data; } + + /** Returns a raw pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline ElementType* getData() const noexcept { return data; } + + /** Returns a void pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator void*() const noexcept { return static_cast (data); } + + /** Returns a void pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator const void*() const noexcept { return static_cast (data); } + + /** Lets you use indirect calls to the first element in the array. + Obviously this will cause problems if the array hasn't been initialised, because it'll + be referencing a null pointer. + */ + inline ElementType* operator->() const noexcept { return data; } + + /** Returns a reference to one of the data elements. + Obviously there's no bounds-checking here, as this object is just a dumb pointer and + has no idea of the size it currently has allocated. + */ + template + inline ElementType& operator[] (IndexType index) const noexcept { return data [index]; } + + /** Returns a pointer to a data element at an offset from the start of the array. + This is the same as doing pointer arithmetic on the raw pointer itself. + */ + template + inline ElementType* operator+ (IndexType index) const noexcept { return data + index; } + + //============================================================================== + /** Compares the pointer with another pointer. + This can be handy for checking whether this is a null pointer. + */ + inline bool operator== (const ElementType* const otherPointer) const noexcept { return otherPointer == data; } + + /** Compares the pointer with another pointer. + This can be handy for checking whether this is a null pointer. + */ + inline bool operator!= (const ElementType* const otherPointer) const noexcept { return otherPointer != data; } + + //============================================================================== + /** Allocates a specified amount of memory. + + This uses the normal malloc to allocate an amount of memory for this object. + Any previously allocated memory will be freed by this method. + + The number of bytes allocated will be (newNumElements * elementSize). Normally + you wouldn't need to specify the second parameter, but it can be handy if you need + to allocate a size in bytes rather than in terms of the number of elements. + + The data that is allocated will be freed when this object is deleted, or when you + call free() or any of the allocation methods. + */ + void malloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + std::free (data); + data = static_cast (std::malloc (newNumElements * elementSize)); + throwOnAllocationFailure(); + } + + /** Allocates a specified amount of memory and clears it. + This does the same job as the malloc() method, but clears the memory that it allocates. + */ + void calloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + std::free (data); + data = static_cast (std::calloc (newNumElements, elementSize)); + throwOnAllocationFailure(); + } + + /** Allocates a specified amount of memory and optionally clears it. + This does the same job as either malloc() or calloc(), depending on the + initialiseToZero parameter. + */ + void allocate (const size_t newNumElements, bool initialiseToZero) + { + std::free (data); + data = static_cast (initialiseToZero + ? std::calloc (newNumElements, sizeof (ElementType)) + : std::malloc (newNumElements * sizeof (ElementType))); + throwOnAllocationFailure(); + } + + /** Re-allocates a specified amount of memory. + + The semantics of this method are the same as malloc() and calloc(), but it + uses realloc() to keep as much of the existing data as possible. + */ + void realloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + data = static_cast (data == nullptr ? std::malloc (newNumElements * elementSize) + : std::realloc (data, newNumElements * elementSize)); + throwOnAllocationFailure(); + } + + /** Frees any currently-allocated data. + This will free the data and reset this object to be a null pointer. + */ + void free() + { + std::free (data); + data = nullptr; + } + + /** Swaps this object's data with the data of another HeapBlock. + The two objects simply exchange their data pointers. + */ + template + void swapWith (HeapBlock & other) noexcept + { + std::swap (data, other.data); + } + + /** This fills the block with zeros, up to the number of elements specified. + Since the block has no way of knowing its own size, you must make sure that the number of + elements you specify doesn't exceed the allocated size. + */ + void clear (size_t numElements) noexcept + { + zeromem (data, sizeof (ElementType) * numElements); + } + + /** This typedef can be used to get the type of the heapblock's elements. */ + typedef ElementType Type; + +private: + //============================================================================== + ElementType* data; + + void throwOnAllocationFailure() const + { + HeapBlockHelper::ThrowOnFail::check (data); + } + + #if ! (defined (BEAST_DLL) || defined (BEAST_DLL_BUILD)) + BEAST_DECLARE_NON_COPYABLE (HeapBlock) + BEAST_PREVENT_HEAP_ALLOCATION // Creating a 'new HeapBlock' would be missing the point! + #endif +}; + + +#endif // BEAST_HEAPBLOCK_BEASTHEADER diff --git a/modules/beast_core/memory/beast_LeakedObjectDetector.h b/modules/beast_core/memory/beast_LeakedObjectDetector.h new file mode 100644 index 0000000000..40ab2a8651 --- /dev/null +++ b/modules/beast_core/memory/beast_LeakedObjectDetector.h @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_LEAKEDOBJECTDETECTOR_BEASTHEADER +#define BEAST_LEAKEDOBJECTDETECTOR_BEASTHEADER + +#include "../text/beast_String.h" +#include "beast_Atomic.h" + + +//============================================================================== +/** + Embedding an instance of this class inside another class can be used as a low-overhead + way of detecting leaked instances. + + This class keeps an internal static count of the number of instances that are + active, so that when the app is shutdown and the static destructors are called, + it can check whether there are any left-over instances that may have been leaked. + + To use it, use the BEAST_LEAK_DETECTOR macro as a simple way to put one in your + class declaration. Have a look through the beast codebase for examples, it's used + in most of the classes. +*/ +template +class LeakedObjectDetector +{ +public: + //============================================================================== + LeakedObjectDetector() noexcept { ++(getCounter().numObjects); } + LeakedObjectDetector (const LeakedObjectDetector&) noexcept { ++(getCounter().numObjects); } + + ~LeakedObjectDetector() + { + if (--(getCounter().numObjects) < 0) + { + DBG ("*** Dangling pointer deletion! Class: " << getLeakedObjectClassName()); + + /** If you hit this, then you've managed to delete more instances of this class than you've + created.. That indicates that you're deleting some dangling pointers. + + Note that although this assertion will have been triggered during a destructor, it might + not be this particular deletion that's at fault - the incorrect one may have happened + at an earlier point in the program, and simply not been detected until now. + + Most errors like this are caused by using old-fashioned, non-RAII techniques for + your object management. Tut, tut. Always, always use ScopedPointers, OwnedArrays, + ReferenceCountedObjects, etc, and avoid the 'delete' operator at all costs! + */ + jassertfalse; + } + } + +private: + //============================================================================== + class LeakCounter + { + public: + LeakCounter() noexcept {} + + ~LeakCounter() + { + if (numObjects.value > 0) + { + DBG ("*** Leaked objects detected: " << numObjects.value << " instance(s) of class " << getLeakedObjectClassName()); + + /** If you hit this, then you've leaked one or more objects of the type specified by + the 'OwnerClass' template parameter - the name should have been printed by the line above. + + If you're leaking, it's probably because you're using old-fashioned, non-RAII techniques for + your object management. Tut, tut. Always, always use ScopedPointers, OwnedArrays, + ReferenceCountedObjects, etc, and avoid the 'delete' operator at all costs! + */ + jassertfalse; + } + } + + Atomic numObjects; + }; + + static const char* getLeakedObjectClassName() + { + return OwnerClass::getLeakedObjectClassName(); + } + + static LeakCounter& getCounter() noexcept + { + static LeakCounter counter; + return counter; + } +}; + +//============================================================================== +#if DOXYGEN || ! defined (BEAST_LEAK_DETECTOR) + #if (DOXYGEN || BEAST_CHECK_MEMORY_LEAKS) + /** This macro lets you embed a leak-detecting object inside a class. + + To use it, simply declare a BEAST_LEAK_DETECTOR(YourClassName) inside a private section + of the class declaration. E.g. + + @code + class MyClass + { + public: + MyClass(); + void blahBlah(); + + private: + BEAST_LEAK_DETECTOR (MyClass) + }; + @endcode + + @see BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR, LeakedObjectDetector + */ + #define BEAST_LEAK_DETECTOR(OwnerClass) \ + friend class beast::LeakedObjectDetector; \ + static const char* getLeakedObjectClassName() noexcept { return #OwnerClass; } \ + beast::LeakedObjectDetector BEAST_JOIN_MACRO (leakDetector, __LINE__); + #else + #define BEAST_LEAK_DETECTOR(OwnerClass) + #endif +#endif + + +#endif // BEAST_LEAKEDOBJECTDETECTOR_BEASTHEADER diff --git a/modules/beast_core/memory/beast_Memory.h b/modules/beast_core/memory/beast_Memory.h new file mode 100644 index 0000000000..68def8bd8e --- /dev/null +++ b/modules/beast_core/memory/beast_Memory.h @@ -0,0 +1,121 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MEMORY_BEASTHEADER +#define BEAST_MEMORY_BEASTHEADER + +//============================================================================== +/** Fills a block of memory with zeros. */ +inline void zeromem (void* memory, size_t numBytes) noexcept { memset (memory, 0, numBytes); } + +/** Overwrites a structure or object with zeros. */ +template +inline void zerostruct (Type& structure) noexcept { memset (&structure, 0, sizeof (structure)); } + +/** Delete an object pointer, and sets the pointer to null. + + Remember that it's not good c++ practice to use delete directly - always try to use a ScopedPointer + or other automatic lifetime-management system rather than resorting to deleting raw pointers! +*/ +template +inline void deleteAndZero (Type& pointer) { delete pointer; pointer = nullptr; } + +/** A handy function which adds a number of bytes to any type of pointer and returns the result. + This can be useful to avoid casting pointers to a char* and back when you want to move them by + a specific number of bytes, +*/ +template +inline Type* addBytesToPointer (Type* pointer, IntegerType bytes) noexcept { return (Type*) (((char*) pointer) + bytes); } + +/** A handy function which returns the difference between any two pointers, in bytes. + The address of the second pointer is subtracted from the first, and the difference in bytes is returned. +*/ +template +inline int getAddressDifference (Type1* pointer1, Type2* pointer2) noexcept { return (int) (((const char*) pointer1) - (const char*) pointer2); } + +/** If a pointer is non-null, this returns a new copy of the object that it points to, or safely returns + nullptr if the pointer is null. +*/ +template +inline Type* createCopyIfNotNull (const Type* pointer) { return pointer != nullptr ? new Type (*pointer) : nullptr; } + +//============================================================================== +#if BEAST_MAC || BEAST_IOS || DOXYGEN + + /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. + You should use the BEAST_AUTORELEASEPOOL macro to create a local auto-release pool on the stack. + */ + class BEAST_API ScopedAutoReleasePool + { + public: + ScopedAutoReleasePool(); + ~ScopedAutoReleasePool(); + + private: + void* pool; + + BEAST_DECLARE_NON_COPYABLE (ScopedAutoReleasePool) + }; + + /** A macro that can be used to easily declare a local ScopedAutoReleasePool + object for RAII-based obj-C autoreleasing. + Because this may use the \@autoreleasepool syntax, you must follow the macro with + a set of braces to mark the scope of the pool. + */ +#if (BEAST_COMPILER_SUPPORTS_ARC && defined (__OBJC__)) || DOXYGEN + #define BEAST_AUTORELEASEPOOL @autoreleasepool +#else + #define BEAST_AUTORELEASEPOOL const beast::ScopedAutoReleasePool BEAST_JOIN_MACRO (autoReleasePool_, __LINE__); +#endif + +#else + #define BEAST_AUTORELEASEPOOL +#endif + +//============================================================================== +/* In a Windows DLL build, we'll expose some malloc/free functions that live inside the DLL, and use these for + allocating all the objects - that way all beast objects in the DLL and in the host will live in the same heap, + avoiding problems when an object is created in one module and passed across to another where it is deleted. + By piggy-backing on the BEAST_LEAK_DETECTOR macro, these allocators can be injected into most beast classes. +*/ +#if BEAST_MSVC && (defined (BEAST_DLL) || defined (BEAST_DLL_BUILD)) && ! (BEAST_DISABLE_DLL_ALLOCATORS || DOXYGEN) + extern BEAST_API void* beastDLL_malloc (size_t); + extern BEAST_API void beastDLL_free (void*); + + #define BEAST_LEAK_DETECTOR(OwnerClass) public:\ + static void* operator new (size_t sz) { return beast::beastDLL_malloc (sz); } \ + static void* operator new (size_t, void* p) { return p; } \ + static void operator delete (void* p) { beast::beastDLL_free (p); } \ + static void operator delete (void*, void*) {} +#endif + +//============================================================================== +/** (Deprecated) This was a Windows-specific way of checking for object leaks - now please + use the BEAST_LEAK_DETECTOR instead. +*/ +#ifndef beast_UseDebuggingNewOperator + #define beast_UseDebuggingNewOperator +#endif + + +#endif // BEAST_MEMORY_BEASTHEADER diff --git a/modules/beast_core/memory/beast_MemoryBlock.cpp b/modules/beast_core/memory/beast_MemoryBlock.cpp new file mode 100644 index 0000000000..e040dff781 --- /dev/null +++ b/modules/beast_core/memory/beast_MemoryBlock.cpp @@ -0,0 +1,416 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +MemoryBlock::MemoryBlock() noexcept + : size (0) +{ +} + +MemoryBlock::MemoryBlock (const size_t initialSize, const bool initialiseToZero) +{ + if (initialSize > 0) + { + size = initialSize; + data.allocate (initialSize, initialiseToZero); + } + else + { + size = 0; + } +} + +MemoryBlock::MemoryBlock (const MemoryBlock& other) + : size (other.size) +{ + if (size > 0) + { + bassert (other.data != nullptr); + data.malloc (size); + memcpy (data, other.data, size); + } +} + +MemoryBlock::MemoryBlock (const void* const dataToInitialiseFrom, const size_t sizeInBytes) + : size (sizeInBytes) +{ + bassert (((ssize_t) sizeInBytes) >= 0); + + if (size > 0) + { + bassert (dataToInitialiseFrom != nullptr); // non-zero size, but a zero pointer passed-in? + + data.malloc (size); + + if (dataToInitialiseFrom != nullptr) + memcpy (data, dataToInitialiseFrom, size); + } +} + +MemoryBlock::~MemoryBlock() noexcept +{ +} + +MemoryBlock& MemoryBlock::operator= (const MemoryBlock& other) +{ + if (this != &other) + { + setSize (other.size, false); + memcpy (data, other.data, size); + } + + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +MemoryBlock::MemoryBlock (MemoryBlock&& other) noexcept + : data (static_cast &&> (other.data)), + size (other.size) +{ +} + +MemoryBlock& MemoryBlock::operator= (MemoryBlock&& other) noexcept +{ + data = static_cast &&> (other.data); + size = other.size; + return *this; +} +#endif + + +//============================================================================== +bool MemoryBlock::operator== (const MemoryBlock& other) const noexcept +{ + return matches (other.data, other.size); +} + +bool MemoryBlock::operator!= (const MemoryBlock& other) const noexcept +{ + return ! operator== (other); +} + +bool MemoryBlock::matches (const void* dataToCompare, size_t dataSize) const noexcept +{ + return size == dataSize + && memcmp (data, dataToCompare, size) == 0; +} + +//============================================================================== +// this will resize the block to this size +void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) +{ + if (size != newSize) + { + if (newSize <= 0) + { + data.free(); + size = 0; + } + else + { + if (data != nullptr) + { + data.realloc (newSize); + + if (initialiseToZero && (newSize > size)) + zeromem (data + size, newSize - size); + } + else + { + data.allocate (newSize, initialiseToZero); + } + + size = newSize; + } + } +} + +void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) +{ + if (size < minimumSize) + setSize (minimumSize, initialiseToZero); +} + +void MemoryBlock::swapWith (MemoryBlock& other) noexcept +{ + std::swap (size, other.size); + data.swapWith (other.data); +} + +//============================================================================== +void MemoryBlock::fillWith (const uint8 value) noexcept +{ + memset (data, (int) value, size); +} + +void MemoryBlock::append (const void* const srcData, const size_t numBytes) +{ + if (numBytes > 0) + { + bassert (srcData != nullptr); // this must not be null! + const size_t oldSize = size; + setSize (size + numBytes); + memcpy (data + oldSize, srcData, numBytes); + } +} + +void MemoryBlock::replaceWith (const void* const srcData, const size_t numBytes) +{ + if (numBytes > 0) + { + bassert (srcData != nullptr); // this must not be null! + setSize (numBytes); + memcpy (data, srcData, numBytes); + } +} + +void MemoryBlock::insert (const void* const srcData, const size_t numBytes, size_t insertPosition) +{ + if (numBytes > 0) + { + bassert (srcData != nullptr); // this must not be null! + insertPosition = bmin (size, insertPosition); + const size_t trailingDataSize = size - insertPosition; + setSize (size + numBytes, false); + + if (trailingDataSize > 0) + memmove (data + insertPosition + numBytes, + data + insertPosition, + trailingDataSize); + + memcpy (data + insertPosition, srcData, numBytes); + } +} + +void MemoryBlock::removeSection (const size_t startByte, const size_t numBytesToRemove) +{ + if (startByte + numBytesToRemove >= size) + { + setSize (startByte); + } + else if (numBytesToRemove > 0) + { + memmove (data + startByte, + data + startByte + numBytesToRemove, + size - (startByte + numBytesToRemove)); + + setSize (size - numBytesToRemove); + } +} + +void MemoryBlock::copyFrom (const void* const src, int offset, size_t num) noexcept +{ + const char* d = static_cast (src); + + if (offset < 0) + { + d -= offset; + num -= offset; + offset = 0; + } + + if (offset + num > size) + num = size - offset; + + if (num > 0) + memcpy (data + offset, d, num); +} + +void MemoryBlock::copyTo (void* const dst, int offset, size_t num) const noexcept +{ + char* d = static_cast (dst); + + if (offset < 0) + { + zeromem (d, (size_t) -offset); + d -= offset; + + num += offset; + offset = 0; + } + + if (offset + num > size) + { + const size_t newNum = size - offset; + zeromem (d + newNum, num - newNum); + num = newNum; + } + + if (num > 0) + memcpy (d, data + offset, num); +} + +String MemoryBlock::toString() const +{ + return String (CharPointer_UTF8 (data), size); +} + +//============================================================================== +int MemoryBlock::getBitRange (const size_t bitRangeStart, size_t numBits) const noexcept +{ + int res = 0; + + size_t byte = bitRangeStart >> 3; + int offsetInByte = (int) bitRangeStart & 7; + size_t bitsSoFar = 0; + + while (numBits > 0 && (size_t) byte < size) + { + const int bitsThisTime = bmin ((int) numBits, 8 - offsetInByte); + const int mask = (0xff >> (8 - bitsThisTime)) << offsetInByte; + + res |= (((data[byte] & mask) >> offsetInByte) << bitsSoFar); + + bitsSoFar += bitsThisTime; + numBits -= bitsThisTime; + ++byte; + offsetInByte = 0; + } + + return res; +} + +void MemoryBlock::setBitRange (const size_t bitRangeStart, size_t numBits, int bitsToSet) noexcept +{ + size_t byte = bitRangeStart >> 3; + int offsetInByte = (int) bitRangeStart & 7; + unsigned int mask = ~((((unsigned int) 0xffffffff) << (32 - numBits)) >> (32 - numBits)); + + while (numBits > 0 && (size_t) byte < size) + { + const int bitsThisTime = bmin ((int) numBits, 8 - offsetInByte); + + const unsigned int tempMask = (mask << offsetInByte) | ~((((unsigned int) 0xffffffff) >> offsetInByte) << offsetInByte); + const unsigned int tempBits = (unsigned int) bitsToSet << offsetInByte; + + data[byte] = (char) ((data[byte] & tempMask) | tempBits); + + ++byte; + numBits -= bitsThisTime; + bitsToSet >>= bitsThisTime; + mask >>= bitsThisTime; + offsetInByte = 0; + } +} + +//============================================================================== +void MemoryBlock::loadFromHexString (const String& hex) +{ + ensureSize ((size_t) hex.length() >> 1); + char* dest = data; + String::CharPointerType t (hex.getCharPointer()); + + for (;;) + { + int byte = 0; + + for (int loop = 2; --loop >= 0;) + { + byte <<= 4; + + for (;;) + { + const beast_wchar c = t.getAndAdvance(); + + if (c >= '0' && c <= '9') + { + byte |= c - '0'; + break; + } + else if (c >= 'a' && c <= 'z') + { + byte |= c - ('a' - 10); + break; + } + else if (c >= 'A' && c <= 'Z') + { + byte |= c - ('A' - 10); + break; + } + else if (c == 0) + { + setSize (static_cast (dest - data)); + return; + } + } + } + + *dest++ = (char) byte; + } +} + +//============================================================================== +const char* const MemoryBlock::encodingTable = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; + +String MemoryBlock::toBase64Encoding() const +{ + const size_t numChars = ((size << 3) + 5) / 6; + + String destString ((unsigned int) size); // store the length, followed by a '.', and then the data. + const int initialLen = destString.length(); + destString.preallocateBytes (sizeof (String::CharPointerType::CharType) * (size_t) (initialLen + 2 + numChars)); + + String::CharPointerType d (destString.getCharPointer()); + d += initialLen; + d.write ('.'); + + for (size_t i = 0; i < numChars; ++i) + d.write ((beast_wchar) (uint8) encodingTable [getBitRange (i * 6, 6)]); + + d.writeNull(); + return destString; +} + +bool MemoryBlock::fromBase64Encoding (const String& s) +{ + const int startPos = s.indexOfChar ('.') + 1; + + if (startPos <= 0) + return false; + + const int numBytesNeeded = s.substring (0, startPos - 1).getIntValue(); + + setSize ((size_t) numBytesNeeded, true); + + const int numChars = s.length() - startPos; + + String::CharPointerType srcChars (s.getCharPointer()); + srcChars += startPos; + int pos = 0; + + for (int i = 0; i < numChars; ++i) + { + const char c = (char) srcChars.getAndAdvance(); + + for (int j = 0; j < 64; ++j) + { + if (encodingTable[j] == c) + { + setBitRange ((size_t) pos, 6, j); + pos += 6; + break; + } + } + } + + return true; +} diff --git a/modules/beast_core/memory/beast_MemoryBlock.h b/modules/beast_core/memory/beast_MemoryBlock.h new file mode 100644 index 0000000000..54107a4b0f --- /dev/null +++ b/modules/beast_core/memory/beast_MemoryBlock.h @@ -0,0 +1,255 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MEMORYBLOCK_BEASTHEADER +#define BEAST_MEMORYBLOCK_BEASTHEADER + +#include "../text/beast_String.h" +#include "../memory/beast_HeapBlock.h" + + +//============================================================================== +/** + A class to hold a resizable block of raw data. + +*/ +class BEAST_API MemoryBlock +{ +public: + //============================================================================== + /** Create an uninitialised block with 0 size. */ + MemoryBlock() noexcept; + + /** Creates a memory block with a given initial size. + + @param initialSize the size of block to create + @param initialiseToZero whether to clear the memory or just leave it uninitialised + */ + MemoryBlock (const size_t initialSize, + bool initialiseToZero = false); + + /** Creates a copy of another memory block. */ + MemoryBlock (const MemoryBlock& other); + + /** Creates a memory block using a copy of a block of data. + + @param dataToInitialiseFrom some data to copy into this block + @param sizeInBytes how much space to use + */ + MemoryBlock (const void* dataToInitialiseFrom, size_t sizeInBytes); + + /** Destructor. */ + ~MemoryBlock() noexcept; + + /** Copies another memory block onto this one. + + This block will be resized and copied to exactly match the other one. + */ + MemoryBlock& operator= (const MemoryBlock& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + MemoryBlock (MemoryBlock&& other) noexcept; + MemoryBlock& operator= (MemoryBlock&& other) noexcept; + #endif + + //============================================================================== + /** Compares two memory blocks. + + @returns true only if the two blocks are the same size and have identical contents. + */ + bool operator== (const MemoryBlock& other) const noexcept; + + /** Compares two memory blocks. + + @returns true if the two blocks are different sizes or have different contents. + */ + bool operator!= (const MemoryBlock& other) const noexcept; + + /** Returns true if the data in this MemoryBlock matches the raw bytes passed-in. + */ + bool matches (const void* data, size_t dataSize) const noexcept; + + //============================================================================== + /** Returns a void pointer to the data. + + Note that the pointer returned will probably become invalid when the + block is resized. + */ + void* getData() const noexcept { return data; } + + /** Returns a byte from the memory block. + + This returns a reference, so you can also use it to set a byte. + */ + template + char& operator[] (const Type offset) const noexcept { return data [offset]; } + + + //============================================================================== + /** Returns the block's current allocated size, in bytes. */ + size_t getSize() const noexcept { return size; } + + /** Resizes the memory block. + + This will try to keep as much of the block's current content as it can, + and can optionally be made to clear any new space that gets allocated at + the end of the block. + + @param newSize the new desired size for the block + @param initialiseNewSpaceToZero if the block gets enlarged, this determines + whether to clear the new section or just leave it + uninitialised + @see ensureSize + */ + void setSize (const size_t newSize, + bool initialiseNewSpaceToZero = false); + + /** Increases the block's size only if it's smaller than a given size. + + @param minimumSize if the block is already bigger than this size, no action + will be taken; otherwise it will be increased to this size + @param initialiseNewSpaceToZero if the block gets enlarged, this determines + whether to clear the new section or just leave it + uninitialised + @see setSize + */ + void ensureSize (const size_t minimumSize, + bool initialiseNewSpaceToZero = false); + + //============================================================================== + /** Fills the entire memory block with a repeated byte value. + + This is handy for clearing a block of memory to zero. + */ + void fillWith (uint8 valueToUse) noexcept; + + /** Adds another block of data to the end of this one. + The data pointer must not be null. This block's size will be increased accordingly. + */ + void append (const void* data, size_t numBytes); + + /** Resizes this block to the given size and fills its contents from the supplied buffer. + The data pointer must not be null. + */ + void replaceWith (const void* data, size_t numBytes); + + /** Inserts some data into the block. + The dataToInsert pointer must not be null. This block's size will be increased accordingly. + If the insert position lies outside the valid range of the block, it will be clipped to + within the range before being used. + */ + void insert (const void* dataToInsert, size_t numBytesToInsert, size_t insertPosition); + + /** Chops out a section of the block. + + This will remove a section of the memory block and close the gap around it, + shifting any subsequent data downwards and reducing the size of the block. + + If the range specified goes beyond the size of the block, it will be clipped. + */ + void removeSection (size_t startByte, size_t numBytesToRemove); + + //============================================================================== + /** Copies data into this MemoryBlock from a memory address. + + @param srcData the memory location of the data to copy into this block + @param destinationOffset the offset in this block at which the data being copied should begin + @param numBytes how much to copy in (if this goes beyond the size of the memory block, + it will be clipped so not to do anything nasty) + */ + void copyFrom (const void* srcData, + int destinationOffset, + size_t numBytes) noexcept; + + /** Copies data from this MemoryBlock to a memory address. + + @param destData the memory location to write to + @param sourceOffset the offset within this block from which the copied data will be read + @param numBytes how much to copy (if this extends beyond the limits of the memory block, + zeros will be used for that portion of the data) + */ + void copyTo (void* destData, + int sourceOffset, + size_t numBytes) const noexcept; + + //============================================================================== + /** Exchanges the contents of this and another memory block. + No actual copying is required for this, so it's very fast. + */ + void swapWith (MemoryBlock& other) noexcept; + + //============================================================================== + /** Attempts to parse the contents of the block as a zero-terminated UTF8 string. */ + String toString() const; + + //============================================================================== + /** Parses a string of hexadecimal numbers and writes this data into the memory block. + + The block will be resized to the number of valid bytes read from the string. + Non-hex characters in the string will be ignored. + + @see String::toHexString() + */ + void loadFromHexString (const String& sourceHexString); + + //============================================================================== + /** Sets a number of bits in the memory block, treating it as a long binary sequence. */ + void setBitRange (size_t bitRangeStart, + size_t numBits, + int binaryNumberToApply) noexcept; + + /** Reads a number of bits from the memory block, treating it as one long binary sequence */ + int getBitRange (size_t bitRangeStart, + size_t numBitsToRead) const noexcept; + + //============================================================================== + /** Returns a string of characters that represent the binary contents of this block. + + Uses a 64-bit encoding system to allow binary data to be turned into a string + of simple non-extended characters, e.g. for storage in XML. + + @see fromBase64Encoding + */ + String toBase64Encoding() const; + + /** Takes a string of encoded characters and turns it into binary data. + + The string passed in must have been created by to64BitEncoding(), and this + block will be resized to recreate the original data block. + + @see toBase64Encoding + */ + bool fromBase64Encoding (const String& encodedString); + + +private: + //============================================================================== + HeapBlock data; + size_t size; + static const char* const encodingTable; + + BEAST_LEAK_DETECTOR (MemoryBlock) +}; + + +#endif // BEAST_MEMORYBLOCK_BEASTHEADER diff --git a/modules/beast_core/memory/beast_OptionalScopedPointer.h b/modules/beast_core/memory/beast_OptionalScopedPointer.h new file mode 100644 index 0000000000..eb9b45d495 --- /dev/null +++ b/modules/beast_core/memory/beast_OptionalScopedPointer.h @@ -0,0 +1,183 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_OPTIONALSCOPEDPOINTER_BEASTHEADER +#define BEAST_OPTIONALSCOPEDPOINTER_BEASTHEADER + +#include "beast_ScopedPointer.h" + + +//============================================================================== +/** + Holds a pointer to an object which can optionally be deleted when this pointer + goes out of scope. + + This acts in many ways like a ScopedPointer, but allows you to specify whether or + not the object is deleted. + + @see ScopedPointer +*/ +template +class OptionalScopedPointer +{ +public: + //============================================================================== + /** Creates an empty OptionalScopedPointer. */ + OptionalScopedPointer() : shouldDelete (false) {} + + /** Creates an OptionalScopedPointer to point to a given object, and specifying whether + the OptionalScopedPointer will delete it. + + If takeOwnership is true, then the OptionalScopedPointer will act like a ScopedPointer, + deleting the object when it is itself deleted. If this parameter is false, then the + OptionalScopedPointer just holds a normal pointer to the object, and won't delete it. + */ + OptionalScopedPointer (ObjectType* objectToHold, bool takeOwnership) + : object (objectToHold), shouldDelete (takeOwnership) + { + } + + /** Takes ownership of the object that another OptionalScopedPointer holds. + + Like a normal ScopedPointer, the objectToTransferFrom object will become null, + as ownership of the managed object is transferred to this object. + + The flag to indicate whether or not to delete the managed object is also + copied from the source object. + */ + OptionalScopedPointer (OptionalScopedPointer& objectToTransferFrom) + : object (objectToTransferFrom.release()), + shouldDelete (objectToTransferFrom.shouldDelete) + { + } + + /** Takes ownership of the object that another OptionalScopedPointer holds. + + Like a normal ScopedPointer, the objectToTransferFrom object will become null, + as ownership of the managed object is transferred to this object. + + The ownership flag that says whether or not to delete the managed object is also + copied from the source object. + */ + OptionalScopedPointer& operator= (OptionalScopedPointer& objectToTransferFrom) + { + if (object != objectToTransferFrom.object) + { + clear(); + object = objectToTransferFrom.object; + } + + shouldDelete = objectToTransferFrom.shouldDelete; + return *this; + } + + /** The destructor may or may not delete the object that is being held, depending on the + takeOwnership flag that was specified when the object was first passed into an + OptionalScopedPointer constructor. + */ + ~OptionalScopedPointer() + { + clear(); + } + + //============================================================================== + /** Returns the object that this pointer is managing. */ + inline operator ObjectType*() const noexcept { return object; } + + /** Returns the object that this pointer is managing. */ + inline ObjectType* get() const noexcept { return object; } + + /** Returns the object that this pointer is managing. */ + inline ObjectType& operator*() const noexcept { return *object; } + + /** Lets you access methods and properties of the object that this pointer is holding. */ + inline ObjectType* operator->() const noexcept { return object; } + + //============================================================================== + /** Removes the current object from this OptionalScopedPointer without deleting it. + This will return the current object, and set this OptionalScopedPointer to a null pointer. + */ + ObjectType* release() noexcept { return object.release(); } + + /** Resets this pointer to null, possibly deleting the object that it holds, if it has + ownership of it. + */ + void clear() + { + if (! shouldDelete) + object.release(); + } + + /** Makes this OptionalScopedPointer point at a new object, specifying whether the + OptionalScopedPointer will take ownership of the object. + + If takeOwnership is true, then the OptionalScopedPointer will act like a ScopedPointer, + deleting the object when it is itself deleted. If this parameter is false, then the + OptionalScopedPointer just holds a normal pointer to the object, and won't delete it. + */ + void set (ObjectType* newObject, bool takeOwnership) + { + if (object != newObject) + { + clear(); + object = newObject; + } + + shouldDelete = takeOwnership; + } + + /** Makes this OptionalScopedPointer point at a new object, and take ownership of that object. */ + void setOwned (ObjectType* newObject) + { + set (newObject, true); + } + + /** Makes this OptionalScopedPointer point at a new object, but will not take ownership of that object. */ + void setNonOwned (ObjectType* newObject) + { + set (newObject, false); + } + + /** Returns true if the target object will be deleted when this pointer + object is deleted. + */ + bool willDeleteObject() const noexcept { return shouldDelete; } + + //============================================================================== + /** Swaps this object with another OptionalScopedPointer. + The two objects simply exchange their states. + */ + void swapWith (OptionalScopedPointer& other) noexcept + { + object.swapWith (other.object); + std::swap (shouldDelete, other.shouldDelete); + } + +private: + //============================================================================== + ScopedPointer object; + bool shouldDelete; +}; + + +#endif // BEAST_OPTIONALSCOPEDPOINTER_BEASTHEADER diff --git a/modules/beast_core/memory/beast_ReferenceCountedObject.h b/modules/beast_core/memory/beast_ReferenceCountedObject.h new file mode 100644 index 0000000000..fe0e02a05c --- /dev/null +++ b/modules/beast_core/memory/beast_ReferenceCountedObject.h @@ -0,0 +1,395 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_REFERENCECOUNTEDOBJECT_BEASTHEADER +#define BEAST_REFERENCECOUNTEDOBJECT_BEASTHEADER + +#include "beast_Atomic.h" + + +//============================================================================== +/** + Adds reference-counting to an object. + + To add reference-counting to a class, derive it from this class, and + use the ReferenceCountedObjectPtr class to point to it. + + e.g. @code + class MyClass : public ReferenceCountedObject + { + void foo(); + + // This is a neat way of declaring a typedef for a pointer class, + // rather than typing out the full templated name each time.. + typedef ReferenceCountedObjectPtr Ptr; + }; + + MyClass::Ptr p = new MyClass(); + MyClass::Ptr p2 = p; + p = nullptr; + p2->foo(); + @endcode + + Once a new ReferenceCountedObject has been assigned to a pointer, be + careful not to delete the object manually. + + This class uses an Atomic value to hold the reference count, so that it + the pointers can be passed between threads safely. For a faster but non-thread-safe + version, use SingleThreadedReferenceCountedObject instead. + + @see ReferenceCountedObjectPtr, ReferenceCountedArray, SingleThreadedReferenceCountedObject +*/ +class BEAST_API ReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + inline void incReferenceCount() noexcept + { + ++refCount; + } + + /** Decreases the object's reference count. + + If the count gets to zero, the object will be deleted. + */ + inline void decReferenceCount() noexcept + { + bassert (getReferenceCount() > 0); + + if (--refCount == 0) + delete this; + } + + /** Returns the object's current reference count. */ + inline int getReferenceCount() const noexcept { return refCount.get(); } + + +protected: + //============================================================================== + /** Creates the reference-counted object (with an initial ref count of zero). */ + ReferenceCountedObject() + { + } + + /** Destructor. */ + virtual ~ReferenceCountedObject() + { + // it's dangerous to delete an object that's still referenced by something else! + bassert (getReferenceCount() == 0); + } + + /** Resets the reference count to zero without deleting the object. + You should probably never need to use this! + */ + void resetReferenceCount() noexcept + { + refCount = 0; + } + +private: + //============================================================================== + Atomic refCount; + + BEAST_DECLARE_NON_COPYABLE (ReferenceCountedObject) +}; + + +//============================================================================== +/** + Adds reference-counting to an object. + + This is effectively a version of the ReferenceCountedObject class, but which + uses a non-atomic counter, and so is not thread-safe (but which will be more + efficient). + For more details on how to use it, see the ReferenceCountedObject class notes. + + @see ReferenceCountedObject, ReferenceCountedObjectPtr, ReferenceCountedArray +*/ +class BEAST_API SingleThreadedReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + inline void incReferenceCount() noexcept + { + ++refCount; + } + + /** Decreases the object's reference count. + + If the count gets to zero, the object will be deleted. + */ + inline void decReferenceCount() noexcept + { + bassert (getReferenceCount() > 0); + + if (--refCount == 0) + delete this; + } + + /** Returns the object's current reference count. */ + inline int getReferenceCount() const noexcept { return refCount; } + + +protected: + //============================================================================== + /** Creates the reference-counted object (with an initial ref count of zero). */ + SingleThreadedReferenceCountedObject() : refCount (0) {} + + /** Destructor. */ + virtual ~SingleThreadedReferenceCountedObject() + { + // it's dangerous to delete an object that's still referenced by something else! + bassert (getReferenceCount() == 0); + } + +private: + //============================================================================== + int refCount; + + BEAST_DECLARE_NON_COPYABLE (SingleThreadedReferenceCountedObject) +}; + + +//============================================================================== +/** + A smart-pointer class which points to a reference-counted object. + + The template parameter specifies the class of the object you want to point to - the easiest + way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject, + but if you need to, you could roll your own reference-countable class by implementing a pair of + mathods called incReferenceCount() and decReferenceCount(). + + When using this class, you'll probably want to create a typedef to abbreviate the full + templated name - e.g. + @code typedef ReferenceCountedObjectPtr MyClassPtr;@endcode + + @see ReferenceCountedObject, ReferenceCountedObjectArray +*/ +template +class ReferenceCountedObjectPtr +{ +public: + /** The class being referenced by this pointer. */ + typedef ReferenceCountedObjectClass ReferencedType; + + //============================================================================== + /** Creates a pointer to a null object. */ + inline ReferenceCountedObjectPtr() noexcept + : referencedObject (nullptr) + { + } + + /** Creates a pointer to an object. + + This will increment the object's reference-count if it is non-null. + */ + inline ReferenceCountedObjectPtr (ReferenceCountedObjectClass* const refCountedObject) noexcept + : referencedObject (refCountedObject) + { + if (refCountedObject != nullptr) + refCountedObject->incReferenceCount(); + } + + /** Copies another pointer. + This will increment the object's reference-count (if it is non-null). + */ + inline ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (other.referencedObject) + { + if (referencedObject != nullptr) + referencedObject->incReferenceCount(); + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Takes-over the object from another pointer. */ + inline ReferenceCountedObjectPtr (ReferenceCountedObjectPtr&& other) noexcept + : referencedObject (other.referencedObject) + { + other.referencedObject = nullptr; + } + #endif + + /** Copies another pointer. + This will increment the object's reference-count (if it is non-null). + */ + template + inline ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (static_cast (other.get())) + { + if (referencedObject != nullptr) + referencedObject->incReferenceCount(); + } + + /** Changes this pointer to point at a different object. + + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (other.referencedObject); + } + + /** Changes this pointer to point at a different object. + + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + template + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (static_cast (other.get())); + } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Takes-over the object from another pointer. */ + ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectPtr&& other) + { + std::swap (referencedObject, other.referencedObject); + return *this; + } + #endif + + /** Changes this pointer to point at a different object. + + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectClass* const newObject) + { + if (referencedObject != newObject) + { + if (newObject != nullptr) + newObject->incReferenceCount(); + + ReferenceCountedObjectClass* const oldObject = referencedObject; + referencedObject = newObject; + + if (oldObject != nullptr) + oldObject->decReferenceCount(); + } + + return *this; + } + + /** Destructor. + + This will decrement the object's reference-count, and may delete it if it + gets to zero. + */ + inline ~ReferenceCountedObjectPtr() + { + if (referencedObject != nullptr) + referencedObject->decReferenceCount(); + } + + /** Returns the object that this pointer references. + The pointer returned may be zero, of course. + */ + inline operator ReferenceCountedObjectClass*() const noexcept + { + return referencedObject; + } + + // the -> operator is called on the referenced object + inline ReferenceCountedObjectClass* operator->() const noexcept + { + return referencedObject; + } + + /** Returns the object that this pointer references. + The pointer returned may be zero, of course. + */ + inline ReferenceCountedObjectClass* get() const noexcept + { + return referencedObject; + } + + /** Returns the object that this pointer references. + The pointer returned may be zero, of course. + */ + inline ReferenceCountedObjectClass* getObject() const noexcept + { + return referencedObject; + } + +private: + //============================================================================== + ReferenceCountedObjectClass* referencedObject; +}; + + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, ReferenceCountedObjectClass* const object2) noexcept +{ + return object1.get() == object2; +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() == object2.get(); +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator== (ReferenceCountedObjectClass* object1, ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 == object2.get(); +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectClass* object2) noexcept +{ + return object1.get() != object2; +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() != object2.get(); +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator!= (ReferenceCountedObjectClass* object1, ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 != object2.get(); +} + + +#endif // BEAST_REFERENCECOUNTEDOBJECT_BEASTHEADER diff --git a/modules/beast_core/memory/beast_ScopedPointer.h b/modules/beast_core/memory/beast_ScopedPointer.h index e9c6cb16e2..f07b7f2929 100644 --- a/modules/beast_core/memory/beast_ScopedPointer.h +++ b/modules/beast_core/memory/beast_ScopedPointer.h @@ -1,41 +1,132 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== #ifndef BEAST_SCOPEDPOINTER_BEASTHEADER #define BEAST_SCOPEDPOINTER_BEASTHEADER +//============================================================================== +/** + This class holds a pointer which is automatically deleted when this object goes + out of scope. + + Once a pointer has been passed to a ScopedPointer, it will make sure that the pointer + gets deleted when the ScopedPointer is deleted. Using the ScopedPointer on the stack or + as member variables is a good way to use RAII to avoid accidentally leaking dynamically + created objects. + + A ScopedPointer can be used in pretty much the same way that you'd use a normal pointer + to an object. If you use the assignment operator to assign a different object to a + ScopedPointer, the old one will be automatically deleted. + + Important note: The class is designed to hold a pointer to an object, NOT to an array! + It calls delete on its payload, not delete[], so do not give it an array to hold! For + that kind of purpose, you should be using HeapBlock or Array instead. + + A const ScopedPointer is guaranteed not to lose ownership of its object or change the + object to which it points during its lifetime. This means that making a copy of a const + ScopedPointer is impossible, as that would involve the new copy taking ownership from the + old one. + + If you need to get a pointer out of a ScopedPointer without it being deleted, you + can use the release() method. + + Something to note is the main difference between this class and the std::auto_ptr class, + which is that ScopedPointer provides a cast-to-object operator, wheras std::auto_ptr + requires that you always call get() to retrieve the pointer. The advantages of providing + the cast is that you don't need to call get(), so can use the ScopedPointer in pretty much + exactly the same way as a raw pointer. The disadvantage is that the compiler is free to + use the cast in unexpected and sometimes dangerous ways - in particular, it becomes difficult + to return a ScopedPointer as the result of a function. To avoid this causing errors, + ScopedPointer contains an overloaded constructor that should cause a syntax error in these + circumstances, but it does mean that instead of returning a ScopedPointer from a function, + you'd need to return a raw pointer (or use a std::auto_ptr instead). +*/ template class ScopedPointer { public: - inline ScopedPointer() : object (0) + //============================================================================== + /** Creates a ScopedPointer containing a null pointer. */ + inline ScopedPointer() noexcept : object (nullptr) { } - inline ScopedPointer (ObjectType* const objectToTakePossessionOf) + /** Creates a ScopedPointer that owns the specified object. */ + inline ScopedPointer (ObjectType* const objectToTakePossessionOf) noexcept : object (objectToTakePossessionOf) { } - ScopedPointer (ScopedPointer& objectToTransferFrom) + /** Creates a ScopedPointer that takes its pointer from another ScopedPointer. + + Because a pointer can only belong to one ScopedPointer, this transfers + the pointer from the other object to this one, and the other object is reset to + be a null pointer. + */ + ScopedPointer (ScopedPointer& objectToTransferFrom) noexcept : object (objectToTransferFrom.object) { - objectToTransferFrom.object = 0; + objectToTransferFrom.object = nullptr; } + /** Destructor. + This will delete the object that this ScopedPointer currently refers to. + */ inline ~ScopedPointer() { delete object; } + /** Changes this ScopedPointer to point to a new object. + + Because a pointer can only belong to one ScopedPointer, this transfers + the pointer from the other object to this one, and the other object is reset to + be a null pointer. + + If this ScopedPointer already points to an object, that object + will first be deleted. + */ ScopedPointer& operator= (ScopedPointer& objectToTransferFrom) { if (this != objectToTransferFrom.getAddress()) { + // Two ScopedPointers should never be able to refer to the same object - if + // this happens, you must have done something dodgy! + bassert (object == nullptr || object != objectToTransferFrom.object); + ObjectType* const oldObject = object; object = objectToTransferFrom.object; - objectToTransferFrom.object = 0; + objectToTransferFrom.object = nullptr; delete oldObject; } return *this; } + /** Changes this ScopedPointer to point to a new object. + + If this ScopedPointer already points to an object, that object + will first be deleted. + + The pointer that you pass in may be a nullptr. + */ ScopedPointer& operator= (ObjectType* const newObjectToTakePossessionOf) { if (object != newObjectToTakePossessionOf) @@ -48,39 +139,110 @@ public: return *this; } - inline operator ObjectType*() const { return object; } - inline ObjectType* get() const { return object; } - inline ObjectType& operator*() const { return *object; } - inline ObjectType* operator->() const { return object; } - - ObjectType* release() { ObjectType* const o = object; object = 0; return o; } - - void swapWith (ScopedPointer & other) + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + ScopedPointer (ScopedPointer&& other) noexcept + : object (other.object) { + other.object = nullptr; + } + + ScopedPointer& operator= (ScopedPointer&& other) noexcept + { + object = other.object; + other.object = nullptr; + return *this; + } + #endif + + //============================================================================== + /** Returns the object that this ScopedPointer refers to. */ + inline operator ObjectType*() const noexcept { return object; } + + /** Returns the object that this ScopedPointer refers to. */ + inline ObjectType* get() const noexcept { return object; } + + /** Returns the object that this ScopedPointer refers to. */ + inline ObjectType& operator*() const noexcept { return *object; } + + /** Lets you access methods and properties of the object that this ScopedPointer refers to. */ + inline ObjectType* operator->() const noexcept { return object; } + + //============================================================================== + /** Removes the current object from this ScopedPointer without deleting it. + This will return the current object, and set the ScopedPointer to a null pointer. + */ + ObjectType* release() noexcept { ObjectType* const o = object; object = nullptr; return o; } + + //============================================================================== + /** Swaps this object with that of another ScopedPointer. + The two objects simply exchange their pointers. + */ + void swapWith (ScopedPointer & other) noexcept + { + // Two ScopedPointers should never be able to refer to the same object - if + // this happens, you must have done something dodgy! + bassert (object != other.object || this == other.getAddress()); + std::swap (object, other.object); } + /** If the pointer is non-null, this will attempt to return a new copy of the object that is pointed to. + If the pointer is null, this will safely return a nullptr. + */ + inline ObjectType* createCopy() const { return createCopyIfNotNull (object); } + private: + //============================================================================== ObjectType* object; - const ScopedPointer* getAddress() const { return this; } + // (Required as an alternative to the overloaded & operator). + const ScopedPointer* getAddress() const noexcept { return this; } - #ifndef _MSC_VER - ScopedPointer (const ScopedPointer&); - ScopedPointer& operator= (const ScopedPointer&); + #if ! BEAST_MSVC // (MSVC can't deal with multiple copy constructors) + /* The copy constructors are private to stop people accidentally copying a const ScopedPointer + (the compiler would let you do so by implicitly casting the source to its raw object pointer). + + A side effect of this is that in a compiler that doesn't support C++11, you may hit an + error when you write something like this: + + ScopedPointer m = new MyClass(); // Compile error: copy constructor is private. + + Even though the compiler would normally ignore the assignment here, it can't do so when the + copy constructor is private. It's very easy to fix though - just write it like this: + + ScopedPointer m (new MyClass()); // Compiles OK + + It's probably best to use the latter form when writing your object declarations anyway, as + this is a better representation of the code that you actually want the compiler to produce. + */ + BEAST_DECLARE_NON_COPYABLE (ScopedPointer) #endif }; +//============================================================================== +/** Compares a ScopedPointer with another pointer. + This can be handy for checking whether this is a null pointer. +*/ template -bool operator== (const ScopedPointer& pointer1, ObjectType* const pointer2) +bool operator== (const ScopedPointer& pointer1, ObjectType* const pointer2) noexcept { return static_cast (pointer1) == pointer2; } +/** Compares a ScopedPointer with another pointer. + This can be handy for checking whether this is a null pointer. +*/ template -bool operator!= (const ScopedPointer& pointer1, ObjectType* const pointer2) +bool operator!= (const ScopedPointer& pointer1, ObjectType* const pointer2) noexcept { return static_cast (pointer1) != pointer2; } +//============================================================================== +#ifndef DOXYGEN +// NB: This is just here to prevent any silly attempts to call deleteAndZero() on a ScopedPointer. +template +void deleteAndZero (ScopedPointer&) { static_bassert (sizeof (Type) == 12345); } #endif + +#endif // BEAST_SCOPEDPOINTER_BEASTHEADER diff --git a/modules/beast_core/memory/beast_Singleton.h b/modules/beast_core/memory/beast_Singleton.h new file mode 100644 index 0000000000..bb039ea32c --- /dev/null +++ b/modules/beast_core/memory/beast_Singleton.h @@ -0,0 +1,287 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SINGLETON_BEASTHEADER +#define BEAST_SINGLETON_BEASTHEADER + + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + To use this, add the line beast_DeclareSingleton (MyClass, doNotRecreateAfterDeletion) + to the class's definition. + + Then put a macro beast_ImplementSingleton (MyClass) along with the class's + implementation code. + + It's also a very good idea to also add the call clearSingletonInstance() in your class's + destructor, in case it is deleted by other means than deleteInstance() + + Clients can then call the static method MyClass::getInstance() to get a pointer + to the singleton, or MyClass::getInstanceWithoutCreating() which will return 0 if + no instance currently exists. + + e.g. @code + + class MySingleton + { + public: + MySingleton() + { + } + + ~MySingleton() + { + // this ensures that no dangling pointers are left when the + // singleton is deleted. + clearSingletonInstance(); + } + + beast_DeclareSingleton (MySingleton, false) + }; + + beast_ImplementSingleton (MySingleton) + + + // example of usage: + MySingleton* m = MySingleton::getInstance(); // creates the singleton if there isn't already one. + + ... + + MySingleton::deleteInstance(); // safely deletes the singleton (if it's been created). + + @endcode + + If doNotRecreateAfterDeletion = true, it won't allow the object to be created more + than once during the process's lifetime - i.e. after you've created and deleted the + object, getInstance() will refuse to create another one. This can be useful to stop + objects being accidentally re-created during your app's shutdown code. + + If you know that your object will only be created and deleted by a single thread, you + can use the slightly more efficient beast_DeclareSingleton_SingleThreaded() macro instead + of this one. + + @see beast_ImplementSingleton, beast_DeclareSingleton_SingleThreaded +*/ +#define beast_DeclareSingleton(classname, doNotRecreateAfterDeletion) \ +\ + static classname* _singletonInstance; \ + static beast::CriticalSection _singletonLock; \ +\ + static classname* BEAST_CALLTYPE getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + {\ + const beast::ScopedLock sl (_singletonLock); \ +\ + if (_singletonInstance == nullptr) \ + { \ + static bool alreadyInside = false; \ + static bool createdOnceAlready = false; \ +\ + const bool problem = alreadyInside || ((doNotRecreateAfterDeletion) && createdOnceAlready); \ + bassert (! problem); \ + if (! problem) \ + { \ + createdOnceAlready = true; \ + alreadyInside = true; \ + classname* newObject = new classname(); /* (use a stack variable to avoid setting the newObject value before the class has finished its constructor) */ \ + alreadyInside = false; \ +\ + _singletonInstance = newObject; \ + } \ + } \ + } \ +\ + return _singletonInstance; \ + } \ +\ + static inline classname* BEAST_CALLTYPE getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void BEAST_CALLTYPE deleteInstance() \ + { \ + const beast::ScopedLock sl (_singletonLock); \ + if (_singletonInstance != nullptr) \ + { \ + classname* const old = _singletonInstance; \ + _singletonInstance = nullptr; \ + delete old; \ + } \ + } \ +\ + void clearSingletonInstance() noexcept\ + { \ + if (_singletonInstance == this) \ + _singletonInstance = nullptr; \ + } + + +//============================================================================== +/** This is a counterpart to the beast_DeclareSingleton macro. + + After adding the beast_DeclareSingleton to the class definition, this macro has + to be used in the cpp file. +*/ +#define beast_ImplementSingleton(classname) \ +\ + classname* classname::_singletonInstance = nullptr; \ + beast::CriticalSection classname::_singletonLock; + + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + This is exactly the same as beast_DeclareSingleton, but doesn't use a critical + section to make access to it thread-safe. If you know that your object will + only ever be created or deleted by a single thread, then this is a + more efficient version to use. + + If doNotRecreateAfterDeletion = true, it won't allow the object to be created more + than once during the process's lifetime - i.e. after you've created and deleted the + object, getInstance() will refuse to create another one. This can be useful to stop + objects being accidentally re-created during your app's shutdown code. + + See the documentation for beast_DeclareSingleton for more information about + how to use it, the only difference being that you have to use + beast_ImplementSingleton_SingleThreaded instead of beast_ImplementSingleton. + + @see beast_ImplementSingleton_SingleThreaded, beast_DeclareSingleton, beast_DeclareSingleton_SingleThreaded_Minimal +*/ +#define beast_DeclareSingleton_SingleThreaded(classname, doNotRecreateAfterDeletion) \ +\ + static classname* _singletonInstance; \ +\ + static classname* getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + { \ + static bool alreadyInside = false; \ + static bool createdOnceAlready = false; \ +\ + const bool problem = alreadyInside || ((doNotRecreateAfterDeletion) && createdOnceAlready); \ + bassert (! problem); \ + if (! problem) \ + { \ + createdOnceAlready = true; \ + alreadyInside = true; \ + classname* newObject = new classname(); /* (use a stack variable to avoid setting the newObject value before the class has finished its constructor) */ \ + alreadyInside = false; \ +\ + _singletonInstance = newObject; \ + } \ + } \ +\ + return _singletonInstance; \ + } \ +\ + static inline classname* getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void deleteInstance() \ + { \ + if (_singletonInstance != nullptr) \ + { \ + classname* const old = _singletonInstance; \ + _singletonInstance = nullptr; \ + delete old; \ + } \ + } \ +\ + void clearSingletonInstance() noexcept\ + { \ + if (_singletonInstance == this) \ + _singletonInstance = nullptr; \ + } + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + This is like beast_DeclareSingleton_SingleThreaded, but doesn't do any checking + for recursion or repeated instantiation. It's intended for use as a lightweight + version of a singleton, where you're using it in very straightforward + circumstances and don't need the extra checking. + + Beast use the normal beast_ImplementSingleton_SingleThreaded as the counterpart + to this declaration, as you would with beast_DeclareSingleton_SingleThreaded. + + See the documentation for beast_DeclareSingleton for more information about + how to use it, the only difference being that you have to use + beast_ImplementSingleton_SingleThreaded instead of beast_ImplementSingleton. + + @see beast_ImplementSingleton_SingleThreaded, beast_DeclareSingleton +*/ +#define beast_DeclareSingleton_SingleThreaded_Minimal(classname) \ +\ + static classname* _singletonInstance; \ +\ + static classname* getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + _singletonInstance = new classname(); \ +\ + return _singletonInstance; \ + } \ +\ + static inline classname* getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void deleteInstance() \ + { \ + if (_singletonInstance != nullptr) \ + { \ + classname* const old = _singletonInstance; \ + _singletonInstance = nullptr; \ + delete old; \ + } \ + } \ +\ + void clearSingletonInstance() noexcept\ + { \ + if (_singletonInstance == this) \ + _singletonInstance = nullptr; \ + } + + +//============================================================================== +/** This is a counterpart to the beast_DeclareSingleton_SingleThreaded macro. + + After adding beast_DeclareSingleton_SingleThreaded or beast_DeclareSingleton_SingleThreaded_Minimal + to the class definition, this macro has to be used somewhere in the cpp file. +*/ +#define beast_ImplementSingleton_SingleThreaded(classname) \ +\ + classname* classname::_singletonInstance = nullptr; + + + +#endif // BEAST_SINGLETON_BEASTHEADER diff --git a/modules/beast_core/memory/beast_WeakReference.h b/modules/beast_core/memory/beast_WeakReference.h new file mode 100644 index 0000000000..192ef1a623 --- /dev/null +++ b/modules/beast_core/memory/beast_WeakReference.h @@ -0,0 +1,209 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WEAKREFERENCE_BEASTHEADER +#define BEAST_WEAKREFERENCE_BEASTHEADER + +#include "beast_ReferenceCountedObject.h" + + +//============================================================================== +/** + This class acts as a pointer which will automatically become null if the object + to which it points is deleted. + + To accomplish this, the source object needs to cooperate by performing a couple of simple tasks. + It must embed a WeakReference::Master object, which stores a shared pointer object, and must clear + this master pointer in its destructor. + + E.g. + @code + class MyObject + { + public: + MyObject() + { + // If you're planning on using your WeakReferences in a multi-threaded situation, you may choose + // to create a WeakReference to the object here in the constructor, which will pre-initialise the + // embedded object, avoiding an (extremely unlikely) race condition that could occur if multiple + // threads overlap while creating the first WeakReference to it. + } + + ~MyObject() + { + // This will zero all the references - you need to call this in your destructor. + masterReference.clear(); + } + + private: + // You need to embed a variable of this type, with the name "masterReference" inside your object. If the + // variable is not public, you should make your class a friend of WeakReference so that the + // WeakReference class can access it. + WeakReference::Master masterReference; + friend class WeakReference; + }; + + // Here's an example of using a pointer.. + + MyObject* n = new MyObject(); + WeakReference myObjectRef = n; + + MyObject* pointer1 = myObjectRef; // returns a valid pointer to 'n' + delete n; + MyObject* pointer2 = myObjectRef; // returns a null pointer + @endcode + + @see WeakReference::Master +*/ +template +class WeakReference +{ +public: + /** Creates a null SafePointer. */ + inline WeakReference() noexcept {} + + /** Creates a WeakReference that points at the given object. */ + WeakReference (ObjectType* const object) : holder (getRef (object)) {} + + /** Creates a copy of another WeakReference. */ + WeakReference (const WeakReference& other) noexcept : holder (other.holder) {} + + /** Copies another pointer to this one. */ + WeakReference& operator= (const WeakReference& other) { holder = other.holder; return *this; } + + /** Copies another pointer to this one. */ + WeakReference& operator= (ObjectType* const newObject) { holder = getRef (newObject); return *this; } + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + WeakReference (WeakReference&& other) noexcept : holder (static_cast (other.holder)) {} + WeakReference& operator= (WeakReference&& other) noexcept { holder = static_cast (other.holder); return *this; } + #endif + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + ObjectType* get() const noexcept { return holder != nullptr ? holder->get() : nullptr; } + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + operator ObjectType*() const noexcept { return get(); } + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + ObjectType* operator->() noexcept { return get(); } + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + const ObjectType* operator->() const noexcept { return get(); } + + /** This returns true if this reference has been pointing at an object, but that object has + since been deleted. + + If this reference was only ever pointing at a null pointer, this will return false. Using + operator=() to make this refer to a different object will reset this flag to match the status + of the reference from which you're copying. + */ + bool wasObjectDeleted() const noexcept { return holder != nullptr && holder->get() == nullptr; } + + bool operator== (ObjectType* const object) const noexcept { return get() == object; } + bool operator!= (ObjectType* const object) const noexcept { return get() != object; } + + //============================================================================== + /** This class is used internally by the WeakReference class - don't use it directly + in your code! + @see WeakReference + */ + class SharedPointer : public ReferenceCountingType + { + public: + explicit SharedPointer (ObjectType* const obj) noexcept : owner (obj) {} + + inline ObjectType* get() const noexcept { return owner; } + void clearPointer() noexcept { owner = nullptr; } + + private: + ObjectType* volatile owner; + + BEAST_DECLARE_NON_COPYABLE (SharedPointer) + }; + + typedef ReferenceCountedObjectPtr SharedRef; + + //============================================================================== + /** + This class is embedded inside an object to which you want to attach WeakReference pointers. + See the WeakReference class notes for an example of how to use this class. + @see WeakReference + */ + class Master + { + public: + Master() noexcept {} + + ~Master() + { + // You must remember to call clear() in your source object's destructor! See the notes + // for the WeakReference class for an example of how to do this. + bassert (sharedPointer == nullptr || sharedPointer->get() == nullptr); + } + + /** The first call to this method will create an internal object that is shared by all weak + references to the object. + */ + SharedPointer* getSharedPointer (ObjectType* const object) + { + if (sharedPointer == nullptr) + { + sharedPointer = new SharedPointer (object); + } + else + { + // You're trying to create a weak reference to an object that has already been deleted!! + bassert (sharedPointer->get() != nullptr); + } + + return sharedPointer; + } + + /** The object that owns this master pointer should call this before it gets destroyed, + to zero all the references to this object that may be out there. See the WeakReference + class notes for an example of how to do this. + */ + void clear() + { + if (sharedPointer != nullptr) + sharedPointer->clearPointer(); + } + + private: + SharedRef sharedPointer; + + BEAST_DECLARE_NON_COPYABLE (Master) + }; + +private: + SharedRef holder; + + static inline SharedPointer* getRef (ObjectType* const o) + { + return (o != nullptr) ? o->masterReference.getSharedPointer (o) : nullptr; + } +}; + + +#endif // BEAST_WEAKREFERENCE_BEASTHEADER diff --git a/modules/beast_core/misc/beast_Result.cpp b/modules/beast_core/misc/beast_Result.cpp new file mode 100644 index 0000000000..a61394f08f --- /dev/null +++ b/modules/beast_core/misc/beast_Result.cpp @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +Result::Result (const String& message) noexcept + : errorMessage (message) +{ +} + +Result::Result (const Result& other) + : errorMessage (other.errorMessage) +{ +} + +Result& Result::operator= (const Result& other) +{ + errorMessage = other.errorMessage; + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +Result::Result (Result&& other) noexcept + : errorMessage (static_cast (other.errorMessage)) +{ +} + +Result& Result::operator= (Result&& other) noexcept +{ + errorMessage = static_cast (other.errorMessage); + return *this; +} +#endif + +bool Result::operator== (const Result& other) const noexcept +{ + return errorMessage == other.errorMessage; +} + +bool Result::operator!= (const Result& other) const noexcept +{ + return errorMessage != other.errorMessage; +} + +Result Result::ok() noexcept +{ + return Result (String::empty); +} + +Result Result::fail (const String& errorMessage) noexcept +{ + return Result (errorMessage.isEmpty() ? "Unknown Error" : errorMessage); +} + +const String& Result::getErrorMessage() const noexcept +{ + return errorMessage; +} + +bool Result::wasOk() const noexcept { return errorMessage.isEmpty(); } +Result::operator bool() const noexcept { return errorMessage.isEmpty(); } +bool Result::failed() const noexcept { return errorMessage.isNotEmpty(); } +bool Result::operator!() const noexcept { return errorMessage.isNotEmpty(); } diff --git a/modules/beast_core/misc/beast_Result.h b/modules/beast_core/misc/beast_Result.h new file mode 100644 index 0000000000..137daa830c --- /dev/null +++ b/modules/beast_core/misc/beast_Result.h @@ -0,0 +1,119 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_RESULT_BEASTHEADER +#define BEAST_RESULT_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** + Represents the 'success' or 'failure' of an operation, and holds an associated + error message to describe the error when there's a failure. + + E.g. + @code + Result myOperation() + { + if (doSomeKindOfFoobar()) + return Result::ok(); + else + return Result::fail ("foobar didn't work!"); + } + + const Result result (myOperation()); + + if (result.wasOk()) + { + ...it's all good... + } + else + { + warnUserAboutFailure ("The foobar operation failed! Error message was: " + + result.getErrorMessage()); + } + @endcode +*/ +class BEAST_API Result +{ +public: + //============================================================================== + /** Creates and returns a 'successful' result. */ + static Result ok() noexcept; + + /** Creates a 'failure' result. + If you pass a blank error message in here, a default "Unknown Error" message + will be used instead. + */ + static Result fail (const String& errorMessage) noexcept; + + //============================================================================== + /** Returns true if this result indicates a success. */ + bool wasOk() const noexcept; + + /** Returns true if this result indicates a failure. + You can use getErrorMessage() to retrieve the error message associated + with the failure. + */ + bool failed() const noexcept; + + /** Returns true if this result indicates a success. + This is equivalent to calling wasOk(). + */ + operator bool() const noexcept; + + /** Returns true if this result indicates a failure. + This is equivalent to calling failed(). + */ + bool operator!() const noexcept; + + /** Returns the error message that was set when this result was created. + For a successful result, this will be an empty string; + */ + const String& getErrorMessage() const noexcept; + + //============================================================================== + Result (const Result& other); + Result& operator= (const Result& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + Result (Result&& other) noexcept; + Result& operator= (Result&& other) noexcept; + #endif + + bool operator== (const Result& other) const noexcept; + bool operator!= (const Result& other) const noexcept; + +private: + String errorMessage; + + explicit Result (const String&) noexcept; + + // These casts are private to prevent people trying to use the Result object in numeric contexts + operator int() const; + operator void*() const; +}; + + +#endif // BEAST_RESULT_BEASTHEADER diff --git a/modules/beast_core/misc/beast_Uuid.cpp b/modules/beast_core/misc/beast_Uuid.cpp new file mode 100644 index 0000000000..3a947f848c --- /dev/null +++ b/modules/beast_core/misc/beast_Uuid.cpp @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +namespace +{ + int64 getRandomSeedFromMACAddresses() + { + Array result; + MACAddress::findAllAddresses (result); + + Random r; + for (int i = 0; i < result.size(); ++i) + r.combineSeed (result[i].toInt64()); + + return r.nextInt64(); + } +} + +//============================================================================== +Uuid::Uuid() +{ + // The normal random seeding is pretty good, but we'll throw some MAC addresses + // into the mix too, to make it very very unlikely that two UUIDs will ever be the same.. + + static Random r1 (getRandomSeedFromMACAddresses()); + Random r2; + + for (size_t i = 0; i < sizeof (uuid); ++i) + uuid[i] = (uint8) (r1.nextInt() ^ r2.nextInt()); +} + +Uuid::~Uuid() noexcept {} + +Uuid::Uuid (const Uuid& other) noexcept +{ + memcpy (uuid, other.uuid, sizeof (uuid)); +} + +Uuid& Uuid::operator= (const Uuid& other) noexcept +{ + memcpy (uuid, other.uuid, sizeof (uuid)); + return *this; +} + +bool Uuid::operator== (const Uuid& other) const noexcept { return memcmp (uuid, other.uuid, sizeof (uuid)) == 0; } +bool Uuid::operator!= (const Uuid& other) const noexcept { return ! operator== (other); } + +bool Uuid::isNull() const noexcept +{ + for (size_t i = 0; i < sizeof (uuid); ++i) + if (uuid[i] != 0) + return false; + + return true; +} + +String Uuid::toString() const +{ + return String::toHexString (uuid, sizeof (uuid), 0); +} + +Uuid::Uuid (const String& uuidString) +{ + operator= (uuidString); +} + +Uuid& Uuid::operator= (const String& uuidString) +{ + MemoryBlock mb; + mb.loadFromHexString (uuidString); + mb.ensureSize (sizeof (uuid), true); + mb.copyTo (uuid, 0, sizeof (uuid)); + return *this; +} + +Uuid::Uuid (const uint8* const rawData) +{ + operator= (rawData); +} + +Uuid& Uuid::operator= (const uint8* const rawData) noexcept +{ + if (rawData != nullptr) + memcpy (uuid, rawData, sizeof (uuid)); + else + zeromem (uuid, sizeof (uuid)); + + return *this; +} diff --git a/modules/beast_core/misc/beast_Uuid.h b/modules/beast_core/misc/beast_Uuid.h new file mode 100644 index 0000000000..33f4524c0d --- /dev/null +++ b/modules/beast_core/misc/beast_Uuid.h @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_UUID_BEASTHEADER +#define BEAST_UUID_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** + A universally unique 128-bit identifier. + + This class generates very random unique numbers based on the system time + and MAC addresses if any are available. It's extremely unlikely that two identical + UUIDs would ever be created by chance. + + The class includes methods for saving the ID as a string or as raw binary data. +*/ +class BEAST_API Uuid +{ +public: + //============================================================================== + /** Creates a new unique ID. */ + Uuid(); + + /** Destructor. */ + ~Uuid() noexcept; + + /** Creates a copy of another UUID. */ + Uuid (const Uuid& other) noexcept; + + /** Copies another UUID. */ + Uuid& operator= (const Uuid& other) noexcept; + + //============================================================================== + /** Returns true if the ID is zero. */ + bool isNull() const noexcept; + + bool operator== (const Uuid& other) const noexcept; + bool operator!= (const Uuid& other) const noexcept; + + //============================================================================== + /** Returns a stringified version of this UUID. + + A Uuid object can later be reconstructed from this string using operator= or + the constructor that takes a string parameter. + + @returns a 32 character hex string. + */ + String toString() const; + + /** Creates an ID from an encoded string version. + @see toString + */ + Uuid (const String& uuidString); + + /** Copies from a stringified UUID. + The string passed in should be one that was created with the toString() method. + */ + Uuid& operator= (const String& uuidString); + + + //============================================================================== + /** Returns a pointer to the internal binary representation of the ID. + + This is an array of 16 bytes. To reconstruct a Uuid from its data, use + the constructor or operator= method that takes an array of uint8s. + */ + const uint8* getRawData() const noexcept { return uuid; } + + /** Creates a UUID from a 16-byte array. + @see getRawData + */ + Uuid (const uint8* rawData); + + /** Sets this UUID from 16-bytes of raw data. */ + Uuid& operator= (const uint8* rawData) noexcept; + + +private: + //============================================================================== + uint8 uuid[16]; + + BEAST_LEAK_DETECTOR (Uuid) +}; + + +#endif // BEAST_UUID_BEASTHEADER diff --git a/modules/beast_core/misc/beast_WindowsRegistry.h b/modules/beast_core/misc/beast_WindowsRegistry.h new file mode 100644 index 0000000000..e1c1f811d8 --- /dev/null +++ b/modules/beast_core/misc/beast_WindowsRegistry.h @@ -0,0 +1,119 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WINDOWSREGISTRY_BEASTHEADER +#define BEAST_WINDOWSREGISTRY_BEASTHEADER + +#if BEAST_WINDOWS || DOXYGEN + +/** + Contains some static helper functions for manipulating the MS Windows registry + (Only available on Windows, of course!) +*/ +class WindowsRegistry +{ +public: + //============================================================================== + /** Returns a string from the registry. + The path is a string for the entire path of a value in the registry, + e.g. "HKEY_CURRENT_USER\Software\foo\bar" + */ + static String getValue (const String& regValuePath, + const String& defaultValue = String::empty); + + /** Returns a string from the WOW64 registry. + The path is a string for the entire path of a value in the registry, + e.g. "HKEY_CURRENT_USER\Software\foo\bar" + */ + static String getValueWow64 (const String& regValuePath, + const String& defaultValue = String::empty); + + /** Reads a binary block from the registry. + The path is a string for the entire path of a value in the registry, + e.g. "HKEY_CURRENT_USER\Software\foo\bar" + @returns a DWORD indicating the type of the key. + */ + static uint32 getBinaryValue (const String& regValuePath, MemoryBlock& resultData); + + /** Sets a registry value as a string. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool setValue (const String& regValuePath, const String& value); + + /** Sets a registry value as a DWORD. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool setValue (const String& regValuePath, uint32 value); + + /** Sets a registry value as a QWORD. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool setValue (const String& regValuePath, uint64 value); + + /** Sets a registry value as a binary block. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool setValue (const String& regValuePath, const MemoryBlock& value); + + /** Returns true if the given value exists in the registry. */ + static bool valueExists (const String& regValuePath); + + /** Returns true if the given value exists in the registry. */ + static bool valueExistsWow64 (const String& regValuePath); + + /** Deletes a registry value. */ + static void deleteValue (const String& regValuePath); + + /** Deletes a registry key (which is registry-talk for 'folder'). */ + static void deleteKey (const String& regKeyPath); + + /** Creates a file association in the registry. + + This lets you set the executable that should be launched by a given file extension. + @param fileExtension the file extension to associate, including the + initial dot, e.g. ".txt" + @param symbolicDescription a space-free short token to identify the file type + @param fullDescription a human-readable description of the file type + @param targetExecutable the executable that should be launched + @param iconResourceNumber the icon that gets displayed for the file type will be + found by looking up this resource number in the + executable. Pass 0 here to not use an icon + @param registerForCurrentUserOnly if false, this will try to register the association + for all users (you might not have permission to do this + unless running in an installer). If true, it will register the + association in HKEY_CURRENT_USER. + */ + static bool registerFileAssociation (const String& fileExtension, + const String& symbolicDescription, + const String& fullDescription, + const File& targetExecutable, + int iconResourceNumber, + bool registerForCurrentUserOnly); + +private: + WindowsRegistry(); + BEAST_DECLARE_NON_COPYABLE (WindowsRegistry) +}; + +#endif +#endif // BEAST_WINDOWSREGISTRY_BEASTHEADER diff --git a/modules/beast_core/native/beast_BasicNativeHeaders.h b/modules/beast_core/native/beast_BasicNativeHeaders.h new file mode 100644 index 0000000000..649280f1e2 --- /dev/null +++ b/modules/beast_core/native/beast_BasicNativeHeaders.h @@ -0,0 +1,220 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_BASICNATIVEHEADERS_BEASTHEADER +#define BEAST_BASICNATIVEHEADERS_BEASTHEADER + +#include "../system/beast_TargetPlatform.h" +#undef T + +//============================================================================== +#if BEAST_MAC || BEAST_IOS + + #if BEAST_IOS + #import + #import + #import + #import + #include + #else + #define Point CarbonDummyPointName + #define Component CarbonDummyCompName + #import + #import + #undef Point + #undef Component + #include + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + +//============================================================================== +#elif BEAST_WINDOWS + #if BEAST_MSVC + #ifndef _CPPRTTI + #error "You're compiling without RTTI enabled! This is needed for a lot of BEAST classes, please update your compiler settings!" + #endif + + #ifndef _CPPUNWIND + #error "You're compiling without exceptions enabled! This is needed for a lot of BEAST classes, please update your compiler settings!" + #endif + + #pragma warning (push) + #pragma warning (disable : 4100 4201 4514 4312 4995) + #endif + + #define STRICT 1 + #define WIN32_LEAN_AND_MEAN 1 + #ifndef _WIN32_WINNT + #if BEAST_MINGW + #define _WIN32_WINNT 0x0501 + #else + #define _WIN32_WINNT 0x0600 + #endif + #endif + #define _UNICODE 1 + #define UNICODE 1 + #ifndef _WIN32_IE + #define _WIN32_IE 0x0400 + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #if BEAST_MINGW + #include + #else + #include + #include + #endif + + #undef PACKED + + #if BEAST_MSVC + #pragma warning (pop) + #pragma warning (4: 4511 4512 4100 /*4365*/) // (enable some warnings that are turned off in VC8) + #endif + + #if BEAST_MSVC && ! BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #pragma comment (lib, "kernel32.lib") + #pragma comment (lib, "user32.lib") + #pragma comment (lib, "wininet.lib") + #pragma comment (lib, "advapi32.lib") + #pragma comment (lib, "ws2_32.lib") + #pragma comment (lib, "version.lib") + #pragma comment (lib, "shlwapi.lib") + #pragma comment (lib, "winmm.lib") + + #ifdef _NATIVE_WCHAR_T_DEFINED + #ifdef _DEBUG + #pragma comment (lib, "comsuppwd.lib") + #else + #pragma comment (lib, "comsuppw.lib") + #endif + #else + #ifdef _DEBUG + #pragma comment (lib, "comsuppd.lib") + #else + #pragma comment (lib, "comsupp.lib") + #endif + #endif + #endif + + /* Used with DynamicLibrary to simplify importing functions from a win32 DLL. + + dll: the DynamicLibrary object + functionName: function to import + localFunctionName: name you want to use to actually call it (must be different) + returnType: the return type + params: list of params (bracketed) + */ + #define BEAST_LOAD_WINAPI_FUNCTION(dll, functionName, localFunctionName, returnType, params) \ + typedef returnType (WINAPI *type##localFunctionName) params; \ + type##localFunctionName localFunctionName = (type##localFunctionName) dll.getFunction (#functionName); + +//============================================================================== +#elif BEAST_LINUX + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + +//============================================================================== +#elif BEAST_ANDROID + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +// Need to clear various moronic redefinitions made by system headers.. +#undef max +#undef min +#undef direct +#undef check + +#endif // BEAST_BASICNATIVEHEADERS_BEASTHEADER diff --git a/modules/beast_core/native/beast_android_Files.cpp b/modules/beast_core/native/beast_android_Files.cpp new file mode 100644 index 0000000000..8b6e18315b --- /dev/null +++ b/modules/beast_core/native/beast_android_Files.cpp @@ -0,0 +1,236 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +bool File::copyInternal (const File& dest) const +{ + FileInputStream in (*this); + + if (dest.deleteFile()) + { + { + FileOutputStream out (dest); + + if (out.failedToOpen()) + return false; + + if (out.writeFromInputStream (in, -1) == getSize()) + return true; + } + + dest.deleteFile(); + } + + return false; +} + +void File::findFileSystemRoots (Array& destArray) +{ + destArray.add (File ("/")); +} + +//============================================================================== +bool File::isOnCDRomDrive() const +{ + return false; +} + +bool File::isOnHardDisk() const +{ + return true; +} + +bool File::isOnRemovableDrive() const +{ + return false; +} + +bool File::isHidden() const +{ + return getFileName().startsWithChar ('.'); +} + +//============================================================================== +namespace +{ + File beast_readlink (const String& file, const File& defaultFile) + { + const int size = 8192; + HeapBlock buffer; + buffer.malloc (size + 4); + + const size_t numBytes = readlink (file.toUTF8(), buffer, size); + + if (numBytes > 0 && numBytes <= size) + return File (file).getSiblingFile (String::fromUTF8 (buffer, (int) numBytes)); + + return defaultFile; + } +} + +File File::getLinkedTarget() const +{ + return beast_readlink (getFullPathName().toUTF8(), *this); +} + +//============================================================================== +File File::getSpecialLocation (const SpecialLocationType type) +{ + switch (type) + { + case userHomeDirectory: + case userDocumentsDirectory: + case userMusicDirectory: + case userMoviesDirectory: + case userPicturesDirectory: + case userApplicationDataDirectory: + case userDesktopDirectory: + return File (android.appDataDir); + + case commonApplicationDataDirectory: + return File (android.appDataDir); + + case globalApplicationsDirectory: + return File ("/system/app"); + + case tempDirectory: + //return File (AndroidStatsHelpers::getSystemProperty ("java.io.tmpdir")); + return File (android.appDataDir).getChildFile (".temp"); + + case invokedExecutableFile: + case currentExecutableFile: + case currentApplicationFile: + case hostApplicationPath: + return beast_getExecutableFile(); + + default: + jassertfalse; // unknown type? + break; + } + + return File::nonexistent; +} + +//============================================================================== +String File::getVersion() const +{ + return String::empty; +} + +//============================================================================== +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + // TODO + + return false; +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wildCard_) + : parentDir (File::addTrailingSeparator (directory.getFullPathName())), + wildCard (wildCard_), + dir (opendir (directory.getFullPathName().toUTF8())) + { + } + + ~Pimpl() + { + if (dir != 0) + closedir (dir); + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + if (dir != 0) + { + const char* wildcardUTF8 = nullptr; + + for (;;) + { + struct dirent* const de = readdir (dir); + + if (de == nullptr) + break; + + if (wildcardUTF8 == nullptr) + wildcardUTF8 = wildCard.toUTF8(); + + if (fnmatch (wildcardUTF8, de->d_name, FNM_CASEFOLD) == 0) + { + filenameFound = CharPointer_UTF8 (de->d_name); + + updateStatInfoForFile (parentDir + filenameFound, isDir, fileSize, modTime, creationTime, isReadOnly); + + if (isHidden != 0) + *isHidden = filenameFound.startsWithChar ('.'); + + return true; + } + } + } + + return false; + } + +private: + String parentDir, wildCard; + DIR* dir; + + BEAST_DECLARE_NON_COPYABLE (Pimpl) +}; + + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() +{ +} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} + + +//============================================================================== +bool Process::openDocument (const String& fileName, const String& parameters) +{ + const LocalRef t (javaString (fileName)); + android.activity.callVoidMethod (BeastAppActivity.launchURL, t.get()); +} + +void File::revealToUser() const +{ +} diff --git a/modules/beast_core/native/beast_android_JNIHelpers.h b/modules/beast_core/native/beast_android_JNIHelpers.h new file mode 100644 index 0000000000..ac94be2e07 --- /dev/null +++ b/modules/beast_core/native/beast_android_JNIHelpers.h @@ -0,0 +1,402 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ANDROID_JNIHELPERS_BEASTHEADER +#define BEAST_ANDROID_JNIHELPERS_BEASTHEADER + +#if ! (defined (BEAST_ANDROID_ACTIVITY_CLASSNAME) && defined (BEAST_ANDROID_ACTIVITY_CLASSPATH)) + #error "The BEAST_ANDROID_ACTIVITY_CLASSNAME and BEAST_ANDROID_ACTIVITY_CLASSPATH macros must be set!" +#endif + +//============================================================================== +extern JNIEnv* getEnv() noexcept; + +//============================================================================== +class GlobalRef +{ +public: + inline GlobalRef() noexcept : obj (0) {} + inline explicit GlobalRef (jobject o) : obj (retain (o)) {} + inline GlobalRef (const GlobalRef& other) : obj (retain (other.obj)) {} + ~GlobalRef() { clear(); } + + inline void clear() + { + if (obj != 0) + { + getEnv()->DeleteGlobalRef (obj); + obj = 0; + } + } + + inline GlobalRef& operator= (const GlobalRef& other) + { + jobject newObj = retain (other.obj); + clear(); + obj = newObj; + return *this; + } + + //============================================================================== + inline operator jobject() const noexcept { return obj; } + inline jobject get() const noexcept { return obj; } + + //============================================================================== + #define DECLARE_CALL_TYPE_METHOD(returnType, typeName) \ + returnType call##typeName##Method (jmethodID methodID, ... ) const \ + { \ + va_list args; \ + va_start (args, methodID); \ + returnType result = getEnv()->Call##typeName##MethodV (obj, methodID, args); \ + va_end (args); \ + return result; \ + } + + DECLARE_CALL_TYPE_METHOD (jobject, Object) + DECLARE_CALL_TYPE_METHOD (jboolean, Boolean) + DECLARE_CALL_TYPE_METHOD (jbyte, Byte) + DECLARE_CALL_TYPE_METHOD (jchar, Char) + DECLARE_CALL_TYPE_METHOD (jshort, Short) + DECLARE_CALL_TYPE_METHOD (jint, Int) + DECLARE_CALL_TYPE_METHOD (jlong, Long) + DECLARE_CALL_TYPE_METHOD (jfloat, Float) + DECLARE_CALL_TYPE_METHOD (jdouble, Double) + #undef DECLARE_CALL_TYPE_METHOD + + void callVoidMethod (jmethodID methodID, ... ) const + { + va_list args; + va_start (args, methodID); + getEnv()->CallVoidMethodV (obj, methodID, args); + va_end (args); + } + +private: + //============================================================================== + jobject obj; + + static inline jobject retain (jobject obj) + { + return obj == 0 ? 0 : getEnv()->NewGlobalRef (obj); + } +}; + +//============================================================================== +template +class LocalRef +{ +public: + explicit inline LocalRef (JavaType o) noexcept : obj (o) {} + inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} + ~LocalRef() { clear(); } + + void clear() + { + if (obj != 0) + getEnv()->DeleteLocalRef (obj); + } + + LocalRef& operator= (const LocalRef& other) + { + jobject newObj = retain (other.obj); + clear(); + obj = newObj; + return *this; + } + + inline operator JavaType() const noexcept { return obj; } + inline JavaType get() const noexcept { return obj; } + +private: + JavaType obj; + + static JavaType retain (JavaType obj) + { + return obj == 0 ? 0 : (JavaType) getEnv()->NewLocalRef (obj); + } +}; + +//============================================================================== +namespace +{ + String beastString (JNIEnv* env, jstring s) + { + const char* const utf8 = env->GetStringUTFChars (s, nullptr); + CharPointer_UTF8 utf8CP (utf8); + const String result (utf8CP); + env->ReleaseStringUTFChars (s, utf8); + return result; + } + + String beastString (jstring s) + { + return beastString (getEnv(), s); + } + + LocalRef javaString (const String& s) + { + return LocalRef (getEnv()->NewStringUTF (s.toUTF8())); + } + + LocalRef javaStringFromChar (const beast_wchar c) + { + char utf8[8] = { 0 }; + CharPointer_UTF8 (utf8).write (c); + return LocalRef (getEnv()->NewStringUTF (utf8)); + } +} + +//============================================================================== +class JNIClassBase +{ +public: + explicit JNIClassBase (const char* classPath); + virtual ~JNIClassBase(); + + inline operator jclass() const noexcept { return classRef; } + + static void initialiseAllClasses (JNIEnv*); + static void releaseAllClasses (JNIEnv*); + +protected: + virtual void initialiseFields (JNIEnv*) = 0; + + jmethodID resolveMethod (JNIEnv*, const char* methodName, const char* params); + jmethodID resolveStaticMethod (JNIEnv*, const char* methodName, const char* params); + jfieldID resolveField (JNIEnv*, const char* fieldName, const char* signature); + jfieldID resolveStaticField (JNIEnv*, const char* fieldName, const char* signature); + +private: + const char* const classPath; + jclass classRef; + + static Array& getClasses(); + void initialise (JNIEnv*); + void release (JNIEnv*); + + BEAST_DECLARE_NON_COPYABLE (JNIClassBase) +}; + +//============================================================================== +#define CREATE_JNI_METHOD(methodID, stringName, params) methodID = resolveMethod (env, stringName, params); +#define CREATE_JNI_STATICMETHOD(methodID, stringName, params) methodID = resolveStaticMethod (env, stringName, params); +#define CREATE_JNI_FIELD(fieldID, stringName, signature) fieldID = resolveField (env, stringName, signature); +#define CREATE_JNI_STATICFIELD(fieldID, stringName, signature) fieldID = resolveStaticField (env, stringName, signature); +#define DECLARE_JNI_METHOD(methodID, stringName, params) jmethodID methodID; +#define DECLARE_JNI_FIELD(fieldID, stringName, signature) jfieldID fieldID; + +#define DECLARE_JNI_CLASS(CppClassName, javaPath) \ + class CppClassName ## _Class : public JNIClassBase \ + { \ + public: \ + CppClassName ## _Class() : JNIClassBase (javaPath) {} \ + \ + void initialiseFields (JNIEnv* env) \ + { \ + JNI_CLASS_MEMBERS (CREATE_JNI_METHOD, CREATE_JNI_STATICMETHOD, CREATE_JNI_FIELD, CREATE_JNI_STATICFIELD); \ + } \ + \ + JNI_CLASS_MEMBERS (DECLARE_JNI_METHOD, DECLARE_JNI_METHOD, DECLARE_JNI_FIELD, DECLARE_JNI_FIELD); \ + }; \ + static CppClassName ## _Class CppClassName; + + +//============================================================================== +#define BEAST_JNI_CALLBACK(className, methodName, returnType, params) \ + extern "C" __attribute__ ((visibility("default"))) returnType BEAST_JOIN_MACRO (BEAST_JOIN_MACRO (Java_, className), _ ## methodName) params + +//============================================================================== +class AndroidSystem +{ +public: + AndroidSystem(); + + void initialise (JNIEnv*, jobject activity, jstring appFile, jstring appDataDir); + void shutdown (JNIEnv*); + + //============================================================================== + GlobalRef activity; + String appFile, appDataDir; + int screenWidth, screenHeight; +}; + +extern AndroidSystem android; + +//============================================================================== +class ThreadLocalJNIEnvHolder +{ +public: + ThreadLocalJNIEnvHolder() + : jvm (nullptr) + { + zeromem (threads, sizeof (threads)); + zeromem (envs, sizeof (envs)); + } + + void initialise (JNIEnv* env) + { + // NB: the DLL can be left loaded by the JVM, so the same static + // objects can end up being reused by subsequent runs of the app + zeromem (threads, sizeof (threads)); + zeromem (envs, sizeof (envs)); + + env->GetJavaVM (&jvm); + addEnv (env); + } + + JNIEnv* attach() + { + JNIEnv* env = nullptr; + jvm->AttachCurrentThread (&env, nullptr); + + if (env != nullptr) + addEnv (env); + + return env; + } + + void detach() + { + jvm->DetachCurrentThread(); + + const pthread_t thisThread = pthread_self(); + + SpinLock::ScopedLockType sl (addRemoveLock); + for (int i = 0; i < maxThreads; ++i) + if (threads[i] == thisThread) + threads[i] = 0; + } + + JNIEnv* getOrAttach() noexcept + { + JNIEnv* env = get(); + + if (env == nullptr) + env = attach(); + + bassert (env != nullptr); + return env; + } + + JNIEnv* get() const noexcept + { + const pthread_t thisThread = pthread_self(); + + for (int i = 0; i < maxThreads; ++i) + if (threads[i] == thisThread) + return envs[i]; + + return nullptr; + } + + enum { maxThreads = 32 }; + +private: + JavaVM* jvm; + pthread_t threads [maxThreads]; + JNIEnv* envs [maxThreads]; + SpinLock addRemoveLock; + + void addEnv (JNIEnv* env) + { + SpinLock::ScopedLockType sl (addRemoveLock); + + if (get() == nullptr) + { + const pthread_t thisThread = pthread_self(); + + for (int i = 0; i < maxThreads; ++i) + { + if (threads[i] == 0) + { + envs[i] = env; + threads[i] = thisThread; + return; + } + } + } + + jassertfalse; // too many threads! + } +}; + +extern ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (createNewView, "createNewView", "(Z)L" BEAST_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ + METHOD (deleteView, "deleteView", "(L" BEAST_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ + METHOD (postMessage, "postMessage", "(J)V") \ + METHOD (finish, "finish", "()V") \ + METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ + METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ + METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ + METHOD (renderGlyph, "renderGlyph", "(CLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ + STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;ILjava/lang/StringBuffer;)L" BEAST_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ + METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ + METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ + METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ + METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ + STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ + METHOD (scanFile, "scanFile", "(Ljava/lang/String;)V") + +DECLARE_JNI_CLASS (BeastAppActivity, BEAST_ANDROID_ACTIVITY_CLASSPATH); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(I)V") \ + METHOD (setColor, "setColor", "(I)V") \ + METHOD (setAlpha, "setAlpha", "(I)V") \ + METHOD (setTypeface, "setTypeface", "(Landroid/graphics/Typeface;)Landroid/graphics/Typeface;") \ + METHOD (ascent, "ascent", "()F") \ + METHOD (descent, "descent", "()F") \ + METHOD (setTextSize, "setTextSize", "(F)V") \ + METHOD (getTextWidths, "getTextWidths", "(Ljava/lang/String;[F)I") \ + METHOD (setTextScaleX, "setTextScaleX", "(F)V") \ + METHOD (getTextPath, "getTextPath", "(Ljava/lang/String;IIFFLandroid/graphics/Path;)V") \ + METHOD (setShader, "setShader", "(Landroid/graphics/Shader;)Landroid/graphics/Shader;") \ + +DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "()V") \ + METHOD (setValues, "setValues", "([F)V") \ + +DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(IIII)V") \ + FIELD (left, "left", "I") \ + FIELD (right, "right", "I") \ + FIELD (top, "top", "I") \ + FIELD (bottom, "bottom", "I") \ + +DECLARE_JNI_CLASS (RectClass, "android/graphics/Rect"); +#undef JNI_CLASS_MEMBERS + +#endif // BEAST_ANDROID_JNIHELPERS_BEASTHEADER diff --git a/modules/beast_core/native/beast_android_Misc.cpp b/modules/beast_core/native/beast_android_Misc.cpp new file mode 100644 index 0000000000..d2d236a180 --- /dev/null +++ b/modules/beast_core/native/beast_android_Misc.cpp @@ -0,0 +1,27 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +void Logger::outputDebugString (const String& text) +{ + __android_log_print (ANDROID_LOG_INFO, "BEAST", text.toUTF8()); +} diff --git a/modules/beast_core/native/beast_android_Network.cpp b/modules/beast_core/native/beast_android_Network.cpp new file mode 100644 index 0000000000..6fdd4f6339 --- /dev/null +++ b/modules/beast_core/native/beast_android_Network.cpp @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "()V") \ + METHOD (toString, "toString", "()Ljava/lang/String;") \ + +DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (release, "release", "()V") \ + METHOD (read, "read", "([BI)I") \ + METHOD (getPosition, "getPosition", "()J") \ + METHOD (getTotalLength, "getTotalLength", "()J") \ + METHOD (isExhausted, "isExhausted", "()Z") \ + METHOD (setPosition, "setPosition", "(J)Z") \ + +DECLARE_JNI_CLASS (HTTPStream, BEAST_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream"); +#undef JNI_CLASS_MEMBERS + + +//============================================================================== +void MACAddress::findAllAddresses (Array& result) +{ + // TODO +} + + +bool Process::openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ + // TODO + return false; +} + + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + //============================================================================== + WebInputStream (String address, bool isPost, const MemoryBlock& postData, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers, int timeOutMs, StringPairArray* responseHeaders) + { + if (! address.contains ("://")) + address = "http://" + address; + + JNIEnv* env = getEnv(); + + jbyteArray postDataArray = 0; + + if (postData.getSize() > 0) + { + postDataArray = env->NewByteArray (postData.getSize()); + env->SetByteArrayRegion (postDataArray, 0, postData.getSize(), (const jbyte*) postData.getData()); + } + + LocalRef responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor)); + + stream = GlobalRef (env->CallStaticObjectMethod (BeastAppActivity, + BeastAppActivity.createHTTPStream, + javaString (address).get(), + (jboolean) isPost, + postDataArray, + javaString (headers).get(), + (jint) timeOutMs, + responseHeaderBuffer.get())); + + if (postDataArray != 0) + env->DeleteLocalRef (postDataArray); + + if (stream != 0) + { + StringArray headerLines; + + { + LocalRef headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(), + StringBuffer.toString)); + headerLines.addLines (beastString (env, headersString)); + } + + if (responseHeaders != 0) + { + for (int i = 0; i < headerLines.size(); ++i) + { + const String& header = headerLines[i]; + const String key (header.upToFirstOccurrenceOf (": ", false, false)); + const String value (header.fromFirstOccurrenceOf (": ", false, false)); + const String previousValue ((*responseHeaders) [key]); + + responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + } + } + } + } + + ~WebInputStream() + { + if (stream != 0) + stream.callVoidMethod (HTTPStream.release); + } + + //============================================================================== + bool isExhausted() { return stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted); } + int64 getTotalLength() { return stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0; } + int64 getPosition() { return stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0; } + bool setPosition (int64 wantedPos) { return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos); } + + int read (void* buffer, int bytesToRead) + { + bassert (buffer != nullptr && bytesToRead >= 0); + + if (stream == nullptr) + return 0; + + JNIEnv* env = getEnv(); + + jbyteArray javaArray = env->NewByteArray (bytesToRead); + + int numBytes = stream.callIntMethod (HTTPStream.read, javaArray, (jint) bytesToRead); + + if (numBytes > 0) + env->GetByteArrayRegion (javaArray, 0, numBytes, static_cast (buffer)); + + env->DeleteLocalRef (javaArray); + return numBytes; + } + + //============================================================================== + GlobalRef stream; + +private: + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; + +InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, + OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers, const int timeOutMs, StringPairArray* responseHeaders) +{ + ScopedPointer wi (new WebInputStream (address, isPost, postData, + progressCallback, progressCallbackContext, + headers, timeOutMs, responseHeaders)); + + return wi->stream != 0 ? wi.release() : nullptr; +} diff --git a/modules/beast_core/native/beast_android_SystemStats.cpp b/modules/beast_core/native/beast_android_SystemStats.cpp new file mode 100644 index 0000000000..b79534fee0 --- /dev/null +++ b/modules/beast_core/native/beast_android_SystemStats.cpp @@ -0,0 +1,307 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +JNIClassBase::JNIClassBase (const char* classPath_) + : classPath (classPath_), classRef (0) +{ + getClasses().add (this); +} + +JNIClassBase::~JNIClassBase() +{ + getClasses().removeFirstMatchingValue (this); +} + +Array& JNIClassBase::getClasses() +{ + static Array classes; + return classes; +} + +void JNIClassBase::initialise (JNIEnv* env) +{ + classRef = (jclass) env->NewGlobalRef (env->FindClass (classPath)); + bassert (classRef != 0); + + initialiseFields (env); +} + +void JNIClassBase::release (JNIEnv* env) +{ + env->DeleteGlobalRef (classRef); +} + +void JNIClassBase::initialiseAllClasses (JNIEnv* env) +{ + const Array& classes = getClasses(); + for (int i = classes.size(); --i >= 0;) + classes.getUnchecked(i)->initialise (env); +} + +void JNIClassBase::releaseAllClasses (JNIEnv* env) +{ + const Array& classes = getClasses(); + for (int i = classes.size(); --i >= 0;) + classes.getUnchecked(i)->release (env); +} + +jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetMethodID (classRef, methodName, params); + bassert (m != 0); + return m; +} + +jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetStaticMethodID (classRef, methodName, params); + bassert (m != 0); + return m; +} + +jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetFieldID (classRef, fieldName, signature); + bassert (f != 0); + return f; +} + +jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); + bassert (f != 0); + return f; +} + +//============================================================================== +ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; + +#if BEAST_DEBUG +static bool systemInitialised = false; +#endif + +JNIEnv* getEnv() noexcept +{ + #if BEAST_DEBUG + if (! systemInitialised) + { + DBG ("*** Call to getEnv() when system not initialised"); + jassertfalse; + exit (0); + } + #endif + + return threadLocalJNIEnvHolder.getOrAttach(); +} + +extern "C" jint JNI_OnLoad (JavaVM*, void*) +{ + return JNI_VERSION_1_2; +} + +//============================================================================== +AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0) +{ +} + +void AndroidSystem::initialise (JNIEnv* env, jobject activity_, + jstring appFile_, jstring appDataDir_) +{ + screenWidth = screenHeight = 0; + JNIClassBase::initialiseAllClasses (env); + + threadLocalJNIEnvHolder.initialise (env); + #if BEAST_DEBUG + systemInitialised = true; + #endif + + activity = GlobalRef (activity_); + appFile = beastString (env, appFile_); + appDataDir = beastString (env, appDataDir_); +} + +void AndroidSystem::shutdown (JNIEnv* env) +{ + activity.clear(); + + #if BEAST_DEBUG + systemInitialised = false; + #endif + + JNIClassBase::releaseAllClasses (env); +} + +AndroidSystem android; + +//============================================================================== +namespace AndroidStatsHelpers +{ + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + STATICMETHOD (getProperty, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;") + + DECLARE_JNI_CLASS (SystemClass, "java/lang/System"); + #undef JNI_CLASS_MEMBERS + + //============================================================================== + String getSystemProperty (const String& name) + { + return beastString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (SystemClass, + SystemClass.getProperty, + javaString (name).get()))); + } + + //============================================================================== + String getLocaleValue (bool isRegion) + { + return beastString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (BeastAppActivity, + BeastAppActivity.getLocaleValue, + isRegion))); + } +} + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + return Android; +} + +String SystemStats::getOperatingSystemName() +{ + return "Android " + AndroidStatsHelpers::getSystemProperty ("os.version"); +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if BEAST_64BIT + return true; + #else + return false; + #endif +} + +String SystemStats::getCpuVendor() +{ + return AndroidStatsHelpers::getSystemProperty ("os.arch"); +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + return 0; // TODO +} + +int SystemStats::getMemorySizeInMegabytes() +{ + #if __ANDROID_API__ >= 9 + struct sysinfo sysi; + + if (sysinfo (&sysi) == 0) + return (sysi.totalram * sysi.mem_unit / (1024 * 1024)); + #endif + + return 0; +} + +int SystemStats::getPageSize() +{ + return sysconf (_SC_PAGESIZE); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + const char* user = getenv ("USER"); + + if (user == 0) + { + struct passwd* const pw = getpwuid (getuid()); + if (pw != 0) + user = pw->pw_name; + } + + return CharPointer_UTF8 (user); +} + +String SystemStats::getFullUserName() +{ + return getLogonName(); +} + +String SystemStats::getComputerName() +{ + char name [256] = { 0 }; + if (gethostname (name, sizeof (name) - 1) == 0) + return name; + + return String::empty; +} + + +String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocaleValue (false); } +String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } +String SystemStats::getDisplayLanguage() { return getUserLanguage(); } + +//============================================================================== +SystemStats::CPUFlags::CPUFlags() +{ + // TODO + hasMMX = false; + hasSSE = false; + hasSSE2 = false; + has3DNow = false; + + numCpus = bmax (1, sysconf (_SC_NPROCESSORS_ONLN)); +} + +//============================================================================== +uint32 beast_millisecondsSinceStartup() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return t.tv_sec * 1000 + t.tv_nsec / 1000000; +} + +int64 Time::getHighResolutionTicks() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return (t.tv_sec * (int64) 1000000) + (t.tv_nsec / 1000); +} + +int64 Time::getHighResolutionTicksPerSecond() noexcept +{ + return 1000000; // (microseconds) +} + +double Time::getMillisecondCounterHiRes() noexcept +{ + return getHighResolutionTicks() * 0.001; +} + +bool Time::setSystemTimeToThisTime() const +{ + jassertfalse; + return false; +} diff --git a/modules/beast_core/native/beast_android_Threads.cpp b/modules/beast_core/native/beast_android_Threads.cpp new file mode 100644 index 0000000000..821d65f4ba --- /dev/null +++ b/modules/beast_core/native/beast_android_Threads.cpp @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in beast_posix_SharedCode.h! +*/ + +//============================================================================== +// sets the process to 0=low priority, 1=normal, 2=high, 3=realtime +void Process::setPriority (ProcessPriority prior) +{ + // TODO + + struct sched_param param; + int policy, maxp, minp; + + const int p = (int) prior; + + if (p <= 1) + policy = SCHED_OTHER; + else + policy = SCHED_RR; + + minp = sched_get_priority_min (policy); + maxp = sched_get_priority_max (policy); + + if (p < 2) + param.sched_priority = 0; + else if (p == 2 ) + // Set to middle of lower realtime priority range + param.sched_priority = minp + (maxp - minp) / 4; + else + // Set to middle of higher realtime priority range + param.sched_priority = minp + (3 * (maxp - minp) / 4); + + pthread_setschedparam (pthread_self(), policy, ¶m); +} + +void Process::terminate() +{ + // TODO + exit (0); +} + +BEAST_API bool BEAST_CALLTYPE beast_isRunningUnderDebugger() +{ + return false; +} + +BEAST_API bool BEAST_CALLTYPE Process::isRunningUnderDebugger() +{ + return beast_isRunningUnderDebugger(); +} + +void Process::raisePrivilege() {} +void Process::lowerPrivilege() {} diff --git a/modules/beast_core/native/beast_linux_Files.cpp b/modules/beast_core/native/beast_linux_Files.cpp new file mode 100644 index 0000000000..32c296dfa0 --- /dev/null +++ b/modules/beast_core/native/beast_linux_Files.cpp @@ -0,0 +1,373 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +enum +{ + U_ISOFS_SUPER_MAGIC = 0x9660, // linux/iso_fs.h + U_MSDOS_SUPER_MAGIC = 0x4d44, // linux/msdos_fs.h + U_NFS_SUPER_MAGIC = 0x6969, // linux/nfs_fs.h + U_SMB_SUPER_MAGIC = 0x517B // linux/smb_fs.h +}; + +//============================================================================== +bool File::copyInternal (const File& dest) const +{ + FileInputStream in (*this); + + if (dest.deleteFile()) + { + { + FileOutputStream out (dest); + + if (out.failedToOpen()) + return false; + + if (out.writeFromInputStream (in, -1) == getSize()) + return true; + } + + dest.deleteFile(); + } + + return false; +} + +void File::findFileSystemRoots (Array& destArray) +{ + destArray.add (File ("/")); +} + +//============================================================================== +bool File::isOnCDRomDrive() const +{ + struct statfs buf; + + return statfs (getFullPathName().toUTF8(), &buf) == 0 + && buf.f_type == (short) U_ISOFS_SUPER_MAGIC; +} + +bool File::isOnHardDisk() const +{ + struct statfs buf; + + if (statfs (getFullPathName().toUTF8(), &buf) == 0) + { + switch (buf.f_type) + { + case U_ISOFS_SUPER_MAGIC: // CD-ROM + case U_MSDOS_SUPER_MAGIC: // Probably floppy (but could be mounted FAT filesystem) + case U_NFS_SUPER_MAGIC: // Network NFS + case U_SMB_SUPER_MAGIC: // Network Samba + return false; + + default: + // Assume anything else is a hard-disk (but note it could + // be a RAM disk. There isn't a good way of determining + // this for sure) + return true; + } + } + + // Assume so if this fails for some reason + return true; +} + +bool File::isOnRemovableDrive() const +{ + jassertfalse; // xxx not implemented for linux! + return false; +} + +bool File::isHidden() const +{ + return getFileName().startsWithChar ('.'); +} + +//============================================================================== +namespace +{ + File beast_readlink (const String& file, const File& defaultFile) + { + const size_t size = 8192; + HeapBlock buffer; + buffer.malloc (size + 4); + + const size_t numBytes = readlink (file.toUTF8(), buffer, size); + + if (numBytes > 0 && numBytes <= size) + return File (file).getSiblingFile (String::fromUTF8 (buffer, (int) numBytes)); + + return defaultFile; + } +} + +File File::getLinkedTarget() const +{ + return beast_readlink (getFullPathName().toUTF8(), *this); +} + +//============================================================================== +static File resolveXDGFolder (const char* const type, const char* const fallbackFolder) +{ + StringArray confLines; + File ("~/.config/user-dirs.dirs").readLines (confLines); + + for (int i = 0; i < confLines.size(); ++i) + { + const String line (confLines[i].trimStart()); + + if (line.startsWith (type)) + { + // eg. resolve XDG_MUSIC_DIR="$HOME/Music" to /home/user/Music + const File f (line.replace ("$HOME", File ("~").getFullPathName()) + .fromFirstOccurrenceOf ("=", false, false) + .trim().unquoted()); + + if (f.isDirectory()) + return f; + } + } + + return File (fallbackFolder); +} + +const char* const* beast_argv = nullptr; +int beast_argc = 0; + +File File::getSpecialLocation (const SpecialLocationType type) +{ + switch (type) + { + case userHomeDirectory: + { + const char* homeDir = getenv ("HOME"); + + if (homeDir == nullptr) + { + struct passwd* const pw = getpwuid (getuid()); + if (pw != nullptr) + homeDir = pw->pw_dir; + } + + return File (CharPointer_UTF8 (homeDir)); + } + + case userDocumentsDirectory: return resolveXDGFolder ("XDG_DOCUMENTS_DIR", "~"); + case userMusicDirectory: return resolveXDGFolder ("XDG_MUSIC_DIR", "~"); + case userMoviesDirectory: return resolveXDGFolder ("XDG_VIDEOS_DIR", "~"); + case userPicturesDirectory: return resolveXDGFolder ("XDG_PICTURES_DIR", "~"); + case userDesktopDirectory: return resolveXDGFolder ("XDG_DESKTOP_DIR", "~/Desktop"); + case userApplicationDataDirectory: return File ("~"); + case commonApplicationDataDirectory: return File ("/var"); + case globalApplicationsDirectory: return File ("/usr"); + + case tempDirectory: + { + File tmp ("/var/tmp"); + + if (! tmp.isDirectory()) + { + tmp = "/tmp"; + + if (! tmp.isDirectory()) + tmp = File::getCurrentWorkingDirectory(); + } + + return tmp; + } + + case invokedExecutableFile: + if (beast_argv != nullptr && beast_argc > 0) + return File (CharPointer_UTF8 (beast_argv[0])); + // deliberate fall-through... + + case currentExecutableFile: + case currentApplicationFile: + return beast_getExecutableFile(); + + case hostApplicationPath: + return beast_readlink ("/proc/self/exe", beast_getExecutableFile()); + + default: + jassertfalse; // unknown type? + break; + } + + return File::nonexistent; +} + +//============================================================================== +String File::getVersion() const +{ + return String::empty; // xxx not yet implemented +} + +//============================================================================== +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + File trashCan ("~/.Trash"); + + if (! trashCan.isDirectory()) + trashCan = "~/.local/share/Trash/files"; + + if (! trashCan.isDirectory()) + return false; + + return moveFileTo (trashCan.getNonexistentChildFile (getFileNameWithoutExtension(), + getFileExtension())); +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wildCard_) + : parentDir (File::addTrailingSeparator (directory.getFullPathName())), + wildCard (wildCard_), + dir (opendir (directory.getFullPathName().toUTF8())) + { + } + + ~Pimpl() + { + if (dir != nullptr) + closedir (dir); + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + if (dir != nullptr) + { + const char* wildcardUTF8 = nullptr; + + for (;;) + { + struct dirent* const de = readdir (dir); + + if (de == nullptr) + break; + + if (wildcardUTF8 == nullptr) + wildcardUTF8 = wildCard.toUTF8(); + + if (fnmatch (wildcardUTF8, de->d_name, FNM_CASEFOLD) == 0) + { + filenameFound = CharPointer_UTF8 (de->d_name); + + updateStatInfoForFile (parentDir + filenameFound, isDir, fileSize, modTime, creationTime, isReadOnly); + + if (isHidden != nullptr) + *isHidden = filenameFound.startsWithChar ('.'); + + return true; + } + } + } + + return false; + } + +private: + String parentDir, wildCard; + DIR* dir; + + BEAST_DECLARE_NON_COPYABLE (Pimpl) +}; + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() +{ +} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} + + +//============================================================================== +static bool isFileExecutable (const String& filename) +{ + beast_statStruct info; + + return beast_stat (filename, info) + && S_ISREG (info.st_mode) + && access (filename.toUTF8(), X_OK) == 0; +} + +bool Process::openDocument (const String& fileName, const String& parameters) +{ + String cmdString (fileName.replace (" ", "\\ ",false)); + cmdString << " " << parameters; + + if (URL::isProbablyAWebsiteURL (fileName) + || cmdString.startsWithIgnoreCase ("file:") + || URL::isProbablyAnEmailAddress (fileName) + || File::createFileWithoutCheckingPath (fileName).isDirectory() + || ! isFileExecutable (fileName)) + { + // create a command that tries to launch a bunch of likely browsers + const char* const browserNames[] = { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla", + "google-chrome", "chromium-browser", "opera", "konqueror" }; + StringArray cmdLines; + + for (int i = 0; i < numElementsInArray (browserNames); ++i) + cmdLines.add (String (browserNames[i]) + " " + cmdString.trim().quoted()); + + cmdString = cmdLines.joinIntoString (" || "); + } + + const char* const argv[4] = { "/bin/sh", "-c", cmdString.toUTF8(), 0 }; + + const int cpid = fork(); + + if (cpid == 0) + { + setsid(); + + // Child process + execve (argv[0], (char**) argv, environ); + exit (0); + } + + return cpid >= 0; +} + +void File::revealToUser() const +{ + if (isDirectory()) + startAsProcess(); + else if (getParentDirectory().exists()) + getParentDirectory().startAsProcess(); +} diff --git a/modules/beast_core/native/beast_linux_Network.cpp b/modules/beast_core/native/beast_linux_Network.cpp new file mode 100644 index 0000000000..0bf0782abb --- /dev/null +++ b/modules/beast_core/native/beast_linux_Network.cpp @@ -0,0 +1,457 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +void MACAddress::findAllAddresses (Array& result) +{ + const int s = socket (AF_INET, SOCK_DGRAM, 0); + if (s != -1) + { + char buf [1024]; + struct ifconf ifc; + ifc.ifc_len = sizeof (buf); + ifc.ifc_buf = buf; + ioctl (s, SIOCGIFCONF, &ifc); + + for (unsigned int i = 0; i < ifc.ifc_len / sizeof (struct ifreq); ++i) + { + struct ifreq ifr; + strcpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name); + + if (ioctl (s, SIOCGIFFLAGS, &ifr) == 0 + && (ifr.ifr_flags & IFF_LOOPBACK) == 0 + && ioctl (s, SIOCGIFHWADDR, &ifr) == 0) + { + result.addIfNotAlreadyThere (MACAddress ((const uint8*) ifr.ifr_hwaddr.sa_data)); + } + } + + close (s); + } +} + + +bool Process::openEmailWithAttachments (const String& /* targetEmailAddress */, + const String& /* emailSubject */, + const String& /* bodyText */, + const StringArray& /* filesToAttach */) +{ + jassertfalse; // xxx todo + + return false; +} + + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) + : socketHandle (-1), levelsOfRedirection (0), + address (address_), headers (headers_), postData (postData_), position (0), + finished (false), isPost (isPost_), timeOutMs (timeOutMs_) + { + createConnection (progressCallback, progressCallbackContext); + + if (responseHeaders != nullptr && ! isError()) + { + for (int i = 0; i < headerLines.size(); ++i) + { + const String& headersEntry = headerLines[i]; + const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); + const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); + const String previousValue ((*responseHeaders) [key]); + responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + } + } + } + + ~WebInputStream() + { + closeSocket(); + } + + //============================================================================== + bool isError() const { return socketHandle < 0; } + bool isExhausted() { return finished; } + int64 getPosition() { return position; } + + int64 getTotalLength() + { + //xxx to do + return -1; + } + + int read (void* buffer, int bytesToRead) + { + if (finished || isError()) + return 0; + + fd_set readbits; + FD_ZERO (&readbits); + FD_SET (socketHandle, &readbits); + + struct timeval tv; + tv.tv_sec = bmax (1, timeOutMs / 1000); + tv.tv_usec = 0; + + if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) + return 0; // (timeout) + + const int bytesRead = bmax (0, (int) recv (socketHandle, buffer, bytesToRead, MSG_WAITALL)); + if (bytesRead == 0) + finished = true; + position += bytesRead; + return bytesRead; + } + + bool setPosition (int64 wantedPos) + { + if (isError()) + return false; + + if (wantedPos != position) + { + finished = false; + + if (wantedPos < position) + { + closeSocket(); + position = 0; + createConnection (0, 0); + } + + skipNextBytes (wantedPos - position); + } + + return true; + } + + //============================================================================== +private: + int socketHandle, levelsOfRedirection; + StringArray headerLines; + String address, headers; + MemoryBlock postData; + int64 position; + bool finished; + const bool isPost; + const int timeOutMs; + + void closeSocket() + { + if (socketHandle >= 0) + close (socketHandle); + + socketHandle = -1; + levelsOfRedirection = 0; + } + + void createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) + { + closeSocket(); + + uint32 timeOutTime = Time::getMillisecondCounter(); + + if (timeOutMs == 0) + timeOutTime += 60000; + else if (timeOutMs < 0) + timeOutTime = 0xffffffff; + else + timeOutTime += timeOutMs; + + String hostName, hostPath; + int hostPort; + if (! decomposeURL (address, hostName, hostPath, hostPort)) + return; + + String serverName, proxyName, proxyPath; + int proxyPort = 0; + int port = 0; + + const String proxyURL (getenv ("http_proxy")); + if (proxyURL.startsWithIgnoreCase ("http://")) + { + if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) + return; + + serverName = proxyName; + port = proxyPort; + } + else + { + serverName = hostName; + port = hostPort; + } + + struct addrinfo hints; + zerostruct (hints); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + struct addrinfo* result = nullptr; + if (getaddrinfo (serverName.toUTF8(), String (port).toUTF8(), &hints, &result) != 0 || result == 0) + return; + + socketHandle = socket (result->ai_family, result->ai_socktype, 0); + + if (socketHandle == -1) + { + freeaddrinfo (result); + return; + } + + int receiveBufferSize = 16384; + setsockopt (socketHandle, SOL_SOCKET, SO_RCVBUF, (char*) &receiveBufferSize, sizeof (receiveBufferSize)); + setsockopt (socketHandle, SOL_SOCKET, SO_KEEPALIVE, 0, 0); + + #if BEAST_MAC + setsockopt (socketHandle, SOL_SOCKET, SO_NOSIGPIPE, 0, 0); + #endif + + if (connect (socketHandle, result->ai_addr, result->ai_addrlen) == -1) + { + closeSocket(); + freeaddrinfo (result); + return; + } + + freeaddrinfo (result); + + { + const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, + hostPath, address, headers, postData, isPost)); + + if (! sendHeader (socketHandle, requestHeader, timeOutTime, progressCallback, progressCallbackContext)) + { + closeSocket(); + return; + } + } + + String responseHeader (readResponse (socketHandle, timeOutTime)); + + if (responseHeader.isNotEmpty()) + { + headerLines = StringArray::fromLines (responseHeader); + + const int statusCode = responseHeader.fromFirstOccurrenceOf (" ", false, false) + .substring (0, 3).getIntValue(); + + //int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); + //bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); + + String location (findHeaderItem (headerLines, "Location:")); + + if (statusCode >= 300 && statusCode < 400 && location.isNotEmpty()) + { + if (! location.startsWithIgnoreCase ("http://")) + location = "http://" + location; + + if (++levelsOfRedirection <= 3) + { + address = location; + createConnection (progressCallback, progressCallbackContext); + return; + } + } + else + { + levelsOfRedirection = 0; + return; + } + } + + closeSocket(); + } + + //============================================================================== + static String readResponse (const int socketHandle, const uint32 timeOutTime) + { + int bytesRead = 0, numConsecutiveLFs = 0; + MemoryBlock buffer (1024, true); + + while (numConsecutiveLFs < 2 && bytesRead < 32768 + && Time::getMillisecondCounter() <= timeOutTime) + { + fd_set readbits; + FD_ZERO (&readbits); + FD_SET (socketHandle, &readbits); + + struct timeval tv; + tv.tv_sec = bmax (1, (int) (timeOutTime - Time::getMillisecondCounter()) / 1000); + tv.tv_usec = 0; + + if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) + return String::empty; // (timeout) + + buffer.ensureSize (bytesRead + 8, true); + char* const dest = (char*) buffer.getData() + bytesRead; + + if (recv (socketHandle, dest, 1, 0) == -1) + return String::empty; + + const char lastByte = *dest; + ++bytesRead; + + if (lastByte == '\n') + ++numConsecutiveLFs; + else if (lastByte != '\r') + numConsecutiveLFs = 0; + } + + const String header (CharPointer_UTF8 ((const char*) buffer.getData())); + + if (header.startsWithIgnoreCase ("HTTP/")) + return header.trimEnd(); + + return String::empty; + } + + static void writeValueIfNotPresent (MemoryOutputStream& dest, const String& headers, const String& key, const String& value) + { + if (! headers.containsIgnoreCase (key)) + dest << "\r\n" << key << ' ' << value; + } + + static void writeHost (MemoryOutputStream& dest, const bool isPost, const String& path, const String& host, const int port) + { + dest << (isPost ? "POST " : "GET ") << path << " HTTP/1.0\r\nHost: " << host; + + if (port > 0) + dest << ':' << port; + } + + static MemoryBlock createRequestHeader (const String& hostName, const int hostPort, + const String& proxyName, const int proxyPort, + const String& hostPath, const String& originalURL, + const String& userHeaders, const MemoryBlock& postData, + const bool isPost) + { + MemoryOutputStream header; + + if (proxyName.isEmpty()) + writeHost (header, isPost, hostPath, hostName, hostPort); + else + writeHost (header, isPost, originalURL, proxyName, proxyPort); + + writeValueIfNotPresent (header, userHeaders, "User-Agent:", "BEAST/" BEAST_STRINGIFY(BEAST_MAJOR_VERSION) + "." BEAST_STRINGIFY(BEAST_MINOR_VERSION) + "." BEAST_STRINGIFY(BEAST_BUILDNUMBER)); + writeValueIfNotPresent (header, userHeaders, "Connection:", "Close"); + + if (isPost) + writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize())); + + header << "\r\n" << userHeaders + << "\r\n" << postData; + + return header.getMemoryBlock(); + } + + static bool sendHeader (int socketHandle, const MemoryBlock& requestHeader, const uint32 timeOutTime, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) + { + size_t totalHeaderSent = 0; + + while (totalHeaderSent < requestHeader.getSize()) + { + if (Time::getMillisecondCounter() > timeOutTime) + return false; + + const int numToSend = bmin (1024, (int) (requestHeader.getSize() - totalHeaderSent)); + + if (send (socketHandle, static_cast (requestHeader.getData()) + totalHeaderSent, numToSend, 0) != numToSend) + return false; + + totalHeaderSent += numToSend; + + if (progressCallback != nullptr && ! progressCallback (progressCallbackContext, totalHeaderSent, requestHeader.getSize())) + return false; + } + + return true; + } + + static bool decomposeURL (const String& url, String& host, String& path, int& port) + { + if (! url.startsWithIgnoreCase ("http://")) + return false; + + const int nextSlash = url.indexOfChar (7, '/'); + int nextColon = url.indexOfChar (7, ':'); + if (nextColon > nextSlash && nextSlash > 0) + nextColon = -1; + + if (nextColon >= 0) + { + host = url.substring (7, nextColon); + + if (nextSlash >= 0) + port = url.substring (nextColon + 1, nextSlash).getIntValue(); + else + port = url.substring (nextColon + 1).getIntValue(); + } + else + { + port = 80; + + if (nextSlash >= 0) + host = url.substring (7, nextSlash); + else + host = url.substring (7); + } + + if (nextSlash >= 0) + path = url.substring (nextSlash); + else + path = "/"; + + return true; + } + + static String findHeaderItem (const StringArray& lines, const String& itemName) + { + for (int i = 0; i < lines.size(); ++i) + if (lines[i].startsWithIgnoreCase (itemName)) + return lines[i].substring (itemName.length()).trim(); + + return String::empty; + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; + +InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, + OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers, const int timeOutMs, StringPairArray* responseHeaders) +{ + ScopedPointer wi (new WebInputStream (address, isPost, postData, + progressCallback, progressCallbackContext, + headers, timeOutMs, responseHeaders)); + + return wi->isError() ? nullptr : wi.release(); +} diff --git a/modules/beast_core/native/beast_linux_SystemStats.cpp b/modules/beast_core/native/beast_linux_SystemStats.cpp new file mode 100644 index 0000000000..3764f477c4 --- /dev/null +++ b/modules/beast_core/native/beast_linux_SystemStats.cpp @@ -0,0 +1,177 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +void Logger::outputDebugString (const String& text) +{ + std::cerr << text << std::endl; +} + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + return Linux; +} + +String SystemStats::getOperatingSystemName() +{ + return "Linux"; +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if BEAST_64BIT + return true; + #else + //xxx not sure how to find this out?.. + return false; + #endif +} + +//============================================================================== +namespace LinuxStatsHelpers +{ + String getCpuInfo (const char* const key) + { + StringArray lines; + File ("/proc/cpuinfo").readLines (lines); + + for (int i = lines.size(); --i >= 0;) // (NB - it's important that this runs in reverse order) + if (lines[i].startsWithIgnoreCase (key)) + return lines[i].fromFirstOccurrenceOf (":", false, false).trim(); + + return String::empty; + } +} + +String SystemStats::getCpuVendor() +{ + return LinuxStatsHelpers::getCpuInfo ("vendor_id"); +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + return roundToInt (LinuxStatsHelpers::getCpuInfo ("cpu MHz").getFloatValue()); +} + +int SystemStats::getMemorySizeInMegabytes() +{ + struct sysinfo sysi; + + if (sysinfo (&sysi) == 0) + return (sysi.totalram * sysi.mem_unit / (1024 * 1024)); + + return 0; +} + +int SystemStats::getPageSize() +{ + return sysconf (_SC_PAGESIZE); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + const char* user = getenv ("USER"); + + if (user == nullptr) + { + struct passwd* const pw = getpwuid (getuid()); + if (pw != nullptr) + user = pw->pw_name; + } + + return CharPointer_UTF8 (user); +} + +String SystemStats::getFullUserName() +{ + return getLogonName(); +} + +String SystemStats::getComputerName() +{ + char name [256] = { 0 }; + if (gethostname (name, sizeof (name) - 1) == 0) + return name; + + return String::empty; +} + +String getLocaleValue (nl_item key) +{ + const char* oldLocale = ::setlocale (LC_ALL, ""); + return String (const_cast (nl_langinfo (key))); + ::setlocale (LC_ALL, oldLocale); +} + +String SystemStats::getUserLanguage() { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); } +String SystemStats::getUserRegion() { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); } +String SystemStats::getDisplayLanguage() { return getUserLanguage(); } + +//============================================================================== +SystemStats::CPUFlags::CPUFlags() +{ + const String flags (LinuxStatsHelpers::getCpuInfo ("flags")); + hasMMX = flags.contains ("mmx"); + hasSSE = flags.contains ("sse"); + hasSSE2 = flags.contains ("sse2"); + has3DNow = flags.contains ("3dnow"); + + numCpus = LinuxStatsHelpers::getCpuInfo ("processor").getIntValue() + 1; +} + +//============================================================================== +uint32 beast_millisecondsSinceStartup() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return t.tv_sec * 1000 + t.tv_nsec / 1000000; +} + +int64 Time::getHighResolutionTicks() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return (t.tv_sec * (int64) 1000000) + (t.tv_nsec / 1000); +} + +int64 Time::getHighResolutionTicksPerSecond() noexcept +{ + return 1000000; // (microseconds) +} + +double Time::getMillisecondCounterHiRes() noexcept +{ + return getHighResolutionTicks() * 0.001; +} + +bool Time::setSystemTimeToThisTime() const +{ + timeval t; + t.tv_sec = millisSinceEpoch / 1000; + t.tv_usec = (millisSinceEpoch - t.tv_sec * 1000) * 1000; + + return settimeofday (&t, 0) == 0; +} diff --git a/modules/beast_core/native/beast_linux_Threads.cpp b/modules/beast_core/native/beast_linux_Threads.cpp new file mode 100644 index 0000000000..13defab94e --- /dev/null +++ b/modules/beast_core/native/beast_linux_Threads.cpp @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in beast_posix_SharedCode.h! +*/ + +//============================================================================== +void Process::setPriority (const ProcessPriority prior) +{ + const int policy = (prior <= NormalPriority) ? SCHED_OTHER : SCHED_RR; + const int minp = sched_get_priority_min (policy); + const int maxp = sched_get_priority_max (policy); + + struct sched_param param; + + switch (prior) + { + case LowPriority: + case NormalPriority: param.sched_priority = 0; break; + case HighPriority: param.sched_priority = minp + (maxp - minp) / 4; break; + case RealtimePriority: param.sched_priority = minp + (3 * (maxp - minp) / 4); break; + default: jassertfalse; break; + } + + pthread_setschedparam (pthread_self(), policy, ¶m); +} + +void Process::terminate() +{ + exit (0); +} + +BEAST_API bool BEAST_CALLTYPE beast_isRunningUnderDebugger() +{ + static char testResult = 0; + + if (testResult == 0) + { + testResult = (char) ptrace (PT_TRACE_ME, 0, 0, 0); + + if (testResult >= 0) + { + ptrace (PT_DETACH, 0, (caddr_t) 1, 0); + testResult = 1; + } + } + + return testResult < 0; +} + +BEAST_API bool BEAST_CALLTYPE Process::isRunningUnderDebugger() +{ + return beast_isRunningUnderDebugger(); +} + +static void swapUserAndEffectiveUser() +{ + (void) setreuid (geteuid(), getuid()); + (void) setregid (getegid(), getgid()); +} + +void Process::raisePrivilege() { if (geteuid() != 0 && getuid() == 0) swapUserAndEffectiveUser(); } +void Process::lowerPrivilege() { if (geteuid() == 0 && getuid() != 0) swapUserAndEffectiveUser(); } diff --git a/modules/beast_core/native/beast_mac_Files.mm b/modules/beast_core/native/beast_mac_Files.mm new file mode 100644 index 0000000000..ac6a066202 --- /dev/null +++ b/modules/beast_core/native/beast_mac_Files.mm @@ -0,0 +1,480 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in beast_posix_SharedCode.h! +*/ + +//============================================================================== +bool File::copyInternal (const File& dest) const +{ + BEAST_AUTORELEASEPOOL + { + NSFileManager* fm = [NSFileManager defaultManager]; + + return [fm fileExistsAtPath: beastStringToNS (fullPath)] + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + && [fm copyItemAtPath: beastStringToNS (fullPath) + toPath: beastStringToNS (dest.getFullPathName()) + error: nil]; + #else + && [fm copyPath: beastStringToNS (fullPath) + toPath: beastStringToNS (dest.getFullPathName()) + handler: nil]; + #endif + } +} + +void File::findFileSystemRoots (Array& destArray) +{ + destArray.add (File ("/")); +} + + +//============================================================================== +namespace FileHelpers +{ + static bool isFileOnDriveType (const File& f, const char* const* types) + { + struct statfs buf; + + if (beast_doStatFS (f, buf)) + { + const String type (buf.f_fstypename); + + while (*types != 0) + if (type.equalsIgnoreCase (*types++)) + return true; + } + + return false; + } + + static bool isHiddenFile (const String& path) + { + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 + BEAST_AUTORELEASEPOOL + { + NSNumber* hidden = nil; + NSError* err = nil; + + return [[NSURL fileURLWithPath: beastStringToNS (path)] + getResourceValue: &hidden forKey: NSURLIsHiddenKey error: &err] + && [hidden boolValue]; + } + #elif BEAST_IOS + return File (path).getFileName().startsWithChar ('.'); + #else + FSRef ref; + LSItemInfoRecord info; + + return FSPathMakeRefWithOptions ((const UInt8*) path.toRawUTF8(), kFSPathMakeRefDoNotFollowLeafSymlink, &ref, 0) == noErr + && LSCopyItemInfoForRef (&ref, kLSRequestBasicFlagsOnly, &info) == noErr + && (info.flags & kLSItemInfoIsInvisible) != 0; + #endif + } + + #if BEAST_IOS + String getIOSSystemLocation (NSSearchPathDirectory type) + { + return nsStringToBeast ([NSSearchPathForDirectoriesInDomains (type, NSUserDomainMask, YES) + objectAtIndex: 0]); + } + #endif + + static bool launchExecutable (const String& pathAndArguments) + { + const char* const argv[4] = { "/bin/sh", "-c", pathAndArguments.toUTF8(), 0 }; + + const int cpid = fork(); + + if (cpid == 0) + { + // Child process + if (execve (argv[0], (char**) argv, 0) < 0) + exit (0); + } + else + { + if (cpid < 0) + return false; + } + + return true; + } +} + +bool File::isOnCDRomDrive() const +{ + const char* const cdTypes[] = { "cd9660", "cdfs", "cddafs", "udf", 0 }; + + return FileHelpers::isFileOnDriveType (*this, cdTypes); +} + +bool File::isOnHardDisk() const +{ + const char* const nonHDTypes[] = { "nfs", "smbfs", "ramfs", 0 }; + + return ! (isOnCDRomDrive() || FileHelpers::isFileOnDriveType (*this, nonHDTypes)); +} + +bool File::isOnRemovableDrive() const +{ + #if BEAST_IOS + return false; // xxx is this possible? + #else + BEAST_AUTORELEASEPOOL + { + BOOL removable = false; + + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: beastStringToNS (getFullPathName()) + isRemovable: &removable + isWritable: nil + isUnmountable: nil + description: nil + type: nil]; + + return removable; + } + #endif +} + +bool File::isHidden() const +{ + return FileHelpers::isHiddenFile (getFullPathName()); +} + +//============================================================================== +const char* const* beast_argv = nullptr; +int beast_argc = 0; + +File File::getSpecialLocation (const SpecialLocationType type) +{ + BEAST_AUTORELEASEPOOL + { + String resultPath; + + switch (type) + { + case userHomeDirectory: resultPath = nsStringToBeast (NSHomeDirectory()); break; + + #if BEAST_IOS + case userDocumentsDirectory: resultPath = FileHelpers::getIOSSystemLocation (NSDocumentDirectory); break; + case userDesktopDirectory: resultPath = FileHelpers::getIOSSystemLocation (NSDesktopDirectory); break; + + case tempDirectory: + { + File tmp (FileHelpers::getIOSSystemLocation (NSCachesDirectory)); + tmp = tmp.getChildFile (beast_getExecutableFile().getFileNameWithoutExtension()); + tmp.createDirectory(); + return tmp.getFullPathName(); + } + + #else + case userDocumentsDirectory: resultPath = "~/Documents"; break; + case userDesktopDirectory: resultPath = "~/Desktop"; break; + + case tempDirectory: + { + File tmp ("~/Library/Caches/" + beast_getExecutableFile().getFileNameWithoutExtension()); + tmp.createDirectory(); + return tmp.getFullPathName(); + } + #endif + case userMusicDirectory: resultPath = "~/Music"; break; + case userMoviesDirectory: resultPath = "~/Movies"; break; + case userPicturesDirectory: resultPath = "~/Pictures"; break; + case userApplicationDataDirectory: resultPath = "~/Library"; break; + case commonApplicationDataDirectory: resultPath = "/Library"; break; + case globalApplicationsDirectory: resultPath = "/Applications"; break; + + case invokedExecutableFile: + if (beast_argv != nullptr && beast_argc > 0) + return File (CharPointer_UTF8 (beast_argv[0])); + // deliberate fall-through... + + case currentExecutableFile: + return beast_getExecutableFile(); + + case currentApplicationFile: + { + const File exe (beast_getExecutableFile()); + const File parent (exe.getParentDirectory()); + + #if BEAST_IOS + return parent; + #else + return parent.getFullPathName().endsWithIgnoreCase ("Contents/MacOS") + ? parent.getParentDirectory().getParentDirectory() + : exe; + #endif + } + + case hostApplicationPath: + { + unsigned int size = 8192; + HeapBlock buffer; + buffer.calloc (size + 8); + + _NSGetExecutablePath (buffer.getData(), &size); + return String::fromUTF8 (buffer, (int) size); + } + + default: + jassertfalse; // unknown type? + break; + } + + if (resultPath.isNotEmpty()) + return File (resultPath.convertToPrecomposedUnicode()); + } + + return File::nonexistent; +} + +//============================================================================== +String File::getVersion() const +{ + BEAST_AUTORELEASEPOOL + { + if (NSBundle* bundle = [NSBundle bundleWithPath: beastStringToNS (getFullPathName())]) + if (NSDictionary* info = [bundle infoDictionary]) + if (NSString* name = [info valueForKey: nsStringLiteral ("CFBundleShortVersionString")]) + return nsStringToBeast (name); + } + + return String::empty; +} + +//============================================================================== +File File::getLinkedTarget() const +{ + #if BEAST_IOS || (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + NSString* dest = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: beastStringToNS (getFullPathName()) error: nil]; + #else + // (the cast here avoids a deprecation warning) + NSString* dest = [((id) [NSFileManager defaultManager]) pathContentOfSymbolicLinkAtPath: beastStringToNS (getFullPathName())]; + #endif + + if (dest != nil) + return File (nsStringToBeast (dest)); + + return *this; +} + +//============================================================================== +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + #if BEAST_IOS + return deleteFile(); //xxx is there a trashcan on the iOS? + #else + BEAST_AUTORELEASEPOOL + { + NSString* p = beastStringToNS (getFullPathName()); + + return [[NSWorkspace sharedWorkspace] + performFileOperation: NSWorkspaceRecycleOperation + source: [p stringByDeletingLastPathComponent] + destination: nsEmptyString() + files: [NSArray arrayWithObject: [p lastPathComponent]] + tag: nil ]; + } + #endif +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wildCard_) + : parentDir (File::addTrailingSeparator (directory.getFullPathName())), + wildCard (wildCard_), + enumerator (nil) + { + BEAST_AUTORELEASEPOOL + { + enumerator = [[[NSFileManager defaultManager] enumeratorAtPath: beastStringToNS (directory.getFullPathName())] retain]; + } + } + + ~Pimpl() + { + [enumerator release]; + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + BEAST_AUTORELEASEPOOL + { + const char* wildcardUTF8 = nullptr; + + for (;;) + { + NSString* file; + if (enumerator == nil || (file = [enumerator nextObject]) == nil) + return false; + + [enumerator skipDescendents]; + filenameFound = nsStringToBeast (file); + + if (wildcardUTF8 == nullptr) + wildcardUTF8 = wildCard.toUTF8(); + + if (fnmatch (wildcardUTF8, filenameFound.toUTF8(), FNM_CASEFOLD) != 0) + continue; + + const String fullPath (parentDir + filenameFound); + updateStatInfoForFile (fullPath, isDir, fileSize, modTime, creationTime, isReadOnly); + + if (isHidden != nullptr) + *isHidden = FileHelpers::isHiddenFile (fullPath); + + return true; + } + } + } + +private: + String parentDir, wildCard; + NSDirectoryEnumerator* enumerator; + + BEAST_DECLARE_NON_COPYABLE (Pimpl) +}; + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildcard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildcard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() +{ +} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} + + +//============================================================================== +bool Process::openDocument (const String& fileName, const String& parameters) +{ + #if BEAST_IOS + return [[UIApplication sharedApplication] openURL: [NSURL URLWithString: beastStringToNS (fileName)]]; + #else + BEAST_AUTORELEASEPOOL + { + if (parameters.isEmpty()) + { + return [[NSWorkspace sharedWorkspace] openFile: beastStringToNS (fileName)] + || [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: beastStringToNS (fileName)]]; + } + + bool ok = false; + const File file (fileName); + + if (file.isBundle()) + { + NSMutableArray* urls = [NSMutableArray array]; + + StringArray docs; + docs.addTokens (parameters, true); + for (int i = 0; i < docs.size(); ++i) + [urls addObject: beastStringToNS (docs[i])]; + + ok = [[NSWorkspace sharedWorkspace] openURLs: urls + withAppBundleIdentifier: [[NSBundle bundleWithPath: beastStringToNS (fileName)] bundleIdentifier] + options: 0 + additionalEventParamDescriptor: nil + launchIdentifiers: nil]; + } + else if (file.exists()) + { + ok = FileHelpers::launchExecutable ("\"" + fileName + "\" " + parameters); + } + + return ok; + } + #endif +} + +void File::revealToUser() const +{ + #if ! BEAST_IOS + if (exists()) + [[NSWorkspace sharedWorkspace] selectFile: beastStringToNS (getFullPathName()) inFileViewerRootedAtPath: nsEmptyString()]; + else if (getParentDirectory().exists()) + getParentDirectory().revealToUser(); + #endif +} + +//============================================================================== +OSType File::getMacOSType() const +{ + BEAST_AUTORELEASEPOOL + { + #if BEAST_IOS || (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + NSDictionary* fileDict = [[NSFileManager defaultManager] attributesOfItemAtPath: beastStringToNS (getFullPathName()) error: nil]; + #else + // (the cast here avoids a deprecation warning) + NSDictionary* fileDict = [((id) [NSFileManager defaultManager]) fileAttributesAtPath: beastStringToNS (getFullPathName()) traverseLink: NO]; + #endif + + return [fileDict fileHFSTypeCode]; + } +} + +bool File::isBundle() const +{ + #if BEAST_IOS + return false; // xxx can't find a sensible way to do this without trying to open the bundle.. + #else + BEAST_AUTORELEASEPOOL + { + return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: beastStringToNS (getFullPathName())]; + } + #endif +} + +#if BEAST_MAC +void File::addToDock() const +{ + // check that it's not already there... + if (! beast_getOutputFromCommand ("defaults read com.apple.dock persistent-apps").containsIgnoreCase (getFullPathName())) + { + beast_runSystemCommand ("defaults write com.apple.dock persistent-apps -array-add \"tile-datafile-data_CFURLString" + + getFullPathName() + "_CFURLStringType0\""); + + beast_runSystemCommand ("osascript -e \"tell application \\\"Dock\\\" to quit\""); + } +} +#endif diff --git a/modules/beast_core/native/beast_mac_Network.mm b/modules/beast_core/native/beast_mac_Network.mm new file mode 100644 index 0000000000..8515e79678 --- /dev/null +++ b/modules/beast_core/native/beast_mac_Network.mm @@ -0,0 +1,427 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +void MACAddress::findAllAddresses (Array& result) +{ + ifaddrs* addrs = nullptr; + + if (getifaddrs (&addrs) == 0) + { + for (const ifaddrs* cursor = addrs; cursor != nullptr; cursor = cursor->ifa_next) + { + sockaddr_storage* sto = (sockaddr_storage*) cursor->ifa_addr; + if (sto->ss_family == AF_LINK) + { + const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; + + #ifndef IFT_ETHER + #define IFT_ETHER 6 + #endif + + if (sadd->sdl_type == IFT_ETHER) + result.addIfNotAlreadyThere (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); + } + } + + freeifaddrs (addrs); + } +} + +//============================================================================== +bool Process::openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ + #if BEAST_IOS + //xxx probably need to use MFMailComposeViewController + jassertfalse; + return false; + #else + BEAST_AUTORELEASEPOOL + { + String script; + script << "tell application \"Mail\"\r\n" + "set newMessage to make new outgoing message with properties {subject:\"" + << emailSubject.replace ("\"", "\\\"") + << "\", content:\"" + << bodyText.replace ("\"", "\\\"") + << "\" & return & return}\r\n" + "tell newMessage\r\n" + "set visible to true\r\n" + "set sender to \"sdfsdfsdfewf\"\r\n" + "make new to recipient at end of to recipients with properties {address:\"" + << targetEmailAddress + << "\"}\r\n"; + + for (int i = 0; i < filesToAttach.size(); ++i) + { + script << "tell content\r\n" + "make new attachment with properties {file name:\"" + << filesToAttach[i].replace ("\"", "\\\"") + << "\"} at after the last paragraph\r\n" + "end tell\r\n"; + } + + script << "end tell\r\n" + "end tell\r\n"; + + NSAppleScript* s = [[NSAppleScript alloc] initWithSource: beastStringToNS (script)]; + NSDictionary* error = nil; + const bool ok = [s executeAndReturnError: &error] != nil; + [s release]; + + return ok; + } + #endif +} + +//============================================================================== +class URLConnectionState : public Thread +{ +public: + URLConnectionState (NSURLRequest* req) + : Thread ("http connection"), + contentLength (-1), + delegate (nil), + request ([req retain]), + connection (nil), + data ([[NSMutableData data] retain]), + headers (nil), + initialised (false), + hasFailed (false), + hasFinished (false) + { + static DelegateClass cls; + delegate = [cls.createInstance() init]; + DelegateClass::setState (delegate, this); + } + + ~URLConnectionState() + { + stop(); + [connection release]; + [data release]; + [request release]; + [headers release]; + [delegate release]; + } + + bool start (URL::OpenStreamProgressCallback* callback, void* context) + { + startThread(); + + while (isThreadRunning() && ! initialised) + { + if (callback != nullptr) + callback (context, -1, (int) [[request HTTPBody] length]); + + Thread::sleep (1); + } + + return connection != nil && ! hasFailed; + } + + void stop() + { + [connection cancel]; + stopThread (10000); + } + + int read (char* dest, int numBytes) + { + int numDone = 0; + + while (numBytes > 0) + { + const int available = bmin (numBytes, (int) [data length]); + + if (available > 0) + { + const ScopedLock sl (dataLock); + [data getBytes: dest length: (NSUInteger) available]; + [data replaceBytesInRange: NSMakeRange (0, (NSUInteger) available) withBytes: nil length: 0]; + + numDone += available; + numBytes -= available; + dest += available; + } + else + { + if (hasFailed || hasFinished) + break; + + Thread::sleep (1); + } + } + + return numDone; + } + + void didReceiveResponse (NSURLResponse* response) + { + { + const ScopedLock sl (dataLock); + [data setLength: 0]; + } + + initialised = true; + contentLength = [response expectedContentLength]; + + [headers release]; + headers = nil; + + if ([response isKindOfClass: [NSHTTPURLResponse class]]) + headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; + } + + void didFailWithError (NSError* error) + { + DBG (nsStringToBeast ([error description])); (void) error; + hasFailed = true; + initialised = true; + signalThreadShouldExit(); + } + + void didReceiveData (NSData* newData) + { + const ScopedLock sl (dataLock); + [data appendData: newData]; + initialised = true; + } + + void didSendBodyData (int /*totalBytesWritten*/, int /*totalBytesExpected*/) + { + } + + void finishedLoading() + { + hasFinished = true; + initialised = true; + signalThreadShouldExit(); + } + + void run() + { + connection = [[NSURLConnection alloc] initWithRequest: request + delegate: delegate]; + while (! threadShouldExit()) + { + BEAST_AUTORELEASEPOOL + { + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } + } + } + + int64 contentLength; + CriticalSection dataLock; + NSObject* delegate; + NSURLRequest* request; + NSURLConnection* connection; + NSMutableData* data; + NSDictionary* headers; + bool initialised, hasFailed, hasFinished; + +private: + //============================================================================== + struct DelegateClass : public ObjCClass + { + DelegateClass() : ObjCClass ("BEASTAppDelegate_") + { + addIvar ("state"); + + addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); + addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); + addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); + addMethod (@selector (connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:totalBytesExpectedToWrite:), + connectionDidSendBodyData, "v@:@iii"); + addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); + + registerClass(); + } + + static void setState (id self, URLConnectionState* state) { object_setInstanceVariable (self, "state", state); } + static URLConnectionState* getState (id self) { return getIvar (self, "state"); } + + private: + static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) + { + getState (self)->didReceiveResponse (response); + } + + static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) + { + getState (self)->didFailWithError (error); + } + + static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) + { + getState (self)->didReceiveData (newData); + } + + static void connectionDidSendBodyData (id self, SEL, NSURLConnection*, NSInteger, NSInteger totalBytesWritten, NSInteger totalBytesExpected) + { + getState (self)->didSendBodyData (totalBytesWritten, totalBytesExpected); + } + + static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) + { + getState (self)->finishedLoading(); + } + }; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) +}; + + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) + : address (address_), headers (headers_), postData (postData_), position (0), + finished (false), isPost (isPost_), timeOutMs (timeOutMs_) + { + BEAST_AUTORELEASEPOOL + { + createConnection (progressCallback, progressCallbackContext); + + if (responseHeaders != nullptr && connection != nullptr && connection->headers != nil) + { + NSEnumerator* enumerator = [connection->headers keyEnumerator]; + NSString* key; + + while ((key = [enumerator nextObject]) != nil) + responseHeaders->set (nsStringToBeast (key), + nsStringToBeast ((NSString*) [connection->headers objectForKey: key])); + } + } + } + + //============================================================================== + bool isError() const { return connection == nullptr; } + int64 getTotalLength() { return connection == nullptr ? -1 : connection->contentLength; } + bool isExhausted() { return finished; } + int64 getPosition() { return position; } + + int read (void* buffer, int bytesToRead) + { + bassert (buffer != nullptr && bytesToRead >= 0); + + if (finished || isError()) + return 0; + + BEAST_AUTORELEASEPOOL + { + const int bytesRead = connection->read (static_cast (buffer), bytesToRead); + position += bytesRead; + + if (bytesRead == 0) + finished = true; + + return bytesRead; + } + } + + bool setPosition (int64 wantedPos) + { + if (wantedPos != position) + { + finished = false; + + if (wantedPos < position) + { + connection = nullptr; + position = 0; + createConnection (0, 0); + } + + skipNextBytes (wantedPos - position); + } + + return true; + } + +private: + ScopedPointer connection; + String address, headers; + MemoryBlock postData; + int64 position; + bool finished; + const bool isPost; + const int timeOutMs; + + void createConnection (URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + bassert (connection == nullptr); + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: beastStringToNS (address)] + cachePolicy: NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]; + + if (req != nil) + { + [req setHTTPMethod: nsStringLiteral (isPost ? "POST" : "GET")]; + //[req setCachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + + StringArray headerLines; + headerLines.addLines (headers); + headerLines.removeEmptyStrings (true); + + for (int i = 0; i < headerLines.size(); ++i) + { + const String key (headerLines[i].upToFirstOccurrenceOf (":", false, false).trim()); + const String value (headerLines[i].fromFirstOccurrenceOf (":", false, false).trim()); + + if (key.isNotEmpty() && value.isNotEmpty()) + [req addValue: beastStringToNS (value) forHTTPHeaderField: beastStringToNS (key)]; + } + + if (isPost && postData.getSize() > 0) + [req setHTTPBody: [NSData dataWithBytes: postData.getData() + length: postData.getSize()]]; + + connection = new URLConnectionState (req); + + if (! connection->start (progressCallback, progressCallbackContext)) + connection = nullptr; + } + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; + +InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, + OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers, const int timeOutMs, StringPairArray* responseHeaders) +{ + ScopedPointer wi (new WebInputStream (address, isPost, postData, + progressCallback, progressCallbackContext, + headers, timeOutMs, responseHeaders)); + + return wi->isError() ? nullptr : wi.release(); +} diff --git a/modules/beast_core/native/beast_mac_Strings.mm b/modules/beast_core/native/beast_mac_Strings.mm new file mode 100644 index 0000000000..4b1cb88678 --- /dev/null +++ b/modules/beast_core/native/beast_mac_Strings.mm @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +String String::fromCFString (CFStringRef cfString) +{ + if (cfString == 0) + return String::empty; + + CFRange range = { 0, CFStringGetLength (cfString) }; + HeapBlock u ((size_t) range.length + 1); + CFStringGetCharacters (cfString, range, u); + u[range.length] = 0; + + return String (CharPointer_UTF16 ((const CharPointer_UTF16::CharType*) u.getData())); +} + +CFStringRef String::toCFString() const +{ + CharPointer_UTF16 utf16 (toUTF16()); + return CFStringCreateWithCharacters (kCFAllocatorDefault, (const UniChar*) utf16.getAddress(), (CFIndex) utf16.length()); +} + +String String::convertToPrecomposedUnicode() const +{ + #if BEAST_IOS + BEAST_AUTORELEASEPOOL + { + return nsStringToBeast ([beastStringToNS (*this) precomposedStringWithCanonicalMapping]); + } + #else + UnicodeMapping map; + + map.unicodeEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, + kUnicodeNoSubset, + kTextEncodingDefaultFormat); + + map.otherEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, + kUnicodeCanonicalCompVariant, + kTextEncodingDefaultFormat); + + map.mappingVersion = kUnicodeUseLatestMapping; + + UnicodeToTextInfo conversionInfo = 0; + String result; + + if (CreateUnicodeToTextInfo (&map, &conversionInfo) == noErr) + { + const size_t bytesNeeded = CharPointer_UTF16::getBytesRequiredFor (getCharPointer()); + + HeapBlock tempOut; + tempOut.calloc (bytesNeeded + 4); + + ByteCount bytesRead = 0; + ByteCount outputBufferSize = 0; + + if (ConvertFromUnicodeToText (conversionInfo, + bytesNeeded, (ConstUniCharArrayPtr) toUTF16().getAddress(), + kUnicodeDefaultDirectionMask, + 0, 0, 0, 0, + bytesNeeded, &bytesRead, + &outputBufferSize, tempOut) == noErr) + { + result = String (CharPointer_UTF16 ((CharPointer_UTF16::CharType*) tempOut.getData())); + } + + DisposeUnicodeToTextInfo (&conversionInfo); + } + + return result; + #endif +} diff --git a/modules/beast_core/native/beast_mac_SystemStats.mm b/modules/beast_core/native/beast_mac_SystemStats.mm new file mode 100644 index 0000000000..2bfb6a97e2 --- /dev/null +++ b/modules/beast_core/native/beast_mac_SystemStats.mm @@ -0,0 +1,291 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +ScopedAutoReleasePool::ScopedAutoReleasePool() +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +ScopedAutoReleasePool::~ScopedAutoReleasePool() +{ + [((NSAutoreleasePool*) pool) release]; +} + +//============================================================================== +void Logger::outputDebugString (const String& text) +{ + // Would prefer to use std::cerr here, but avoiding it for + // the moment, due to clang JIT linkage problems. + fputs (text.toRawUTF8(), stderr); + fputs ("\n", stderr); + fflush (stderr); +} + +//============================================================================== +namespace SystemStatsHelpers +{ + #if BEAST_INTEL && ! BEAST_NO_INLINE_ASM + static void doCPUID (uint32& a, uint32& b, uint32& c, uint32& d, uint32 type) + { + uint32 la = a, lb = b, lc = c, ld = d; + + asm ("mov %%ebx, %%esi \n\t" + "cpuid \n\t" + "xchg %%esi, %%ebx" + : "=a" (la), "=S" (lb), "=c" (lc), "=d" (ld) : "a" (type) + #if BEAST_64BIT + , "b" (lb), "c" (lc), "d" (ld) + #endif + ); + + a = la; b = lb; c = lc; d = ld; + } + #endif +} + +//============================================================================== +SystemStats::CPUFlags::CPUFlags() +{ + #if BEAST_INTEL && ! BEAST_NO_INLINE_ASM + uint32 familyModel = 0, extFeatures = 0, features = 0, dummy = 0; + SystemStatsHelpers::doCPUID (familyModel, extFeatures, dummy, features, 1); + + hasMMX = (features & (1u << 23)) != 0; + hasSSE = (features & (1u << 25)) != 0; + hasSSE2 = (features & (1u << 26)) != 0; + has3DNow = (extFeatures & (1u << 31)) != 0; + #else + hasMMX = false; + hasSSE = false; + hasSSE2 = false; + has3DNow = false; + #endif + + #if BEAST_IOS || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + numCpus = (int) [[NSProcessInfo processInfo] activeProcessorCount]; + #else + numCpus = (int) MPProcessors(); + #endif +} + +#if BEAST_MAC +struct RLimitInitialiser +{ + RLimitInitialiser() + { + rlimit lim; + getrlimit (RLIMIT_NOFILE, &lim); + lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; + setrlimit (RLIMIT_NOFILE, &lim); + } +}; + +static RLimitInitialiser rLimitInitialiser; +#endif + +//============================================================================== +#if ! BEAST_IOS +static String getOSXVersion() +{ + BEAST_AUTORELEASEPOOL + { + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile: + nsStringLiteral ("/System/Library/CoreServices/SystemVersion.plist")]; + + return nsStringToBeast ([dict objectForKey: nsStringLiteral ("ProductVersion")]); + } +} +#endif + +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + #if BEAST_IOS + return iOS; + #else + StringArray parts; + parts.addTokens (getOSXVersion(), ".", String::empty); + + bassert (parts[0].getIntValue() == 10); + const int major = parts[1].getIntValue(); + bassert (major > 2); + + return (OperatingSystemType) (major + MacOSX_10_4 - 4); + #endif +} + +String SystemStats::getOperatingSystemName() +{ + #if BEAST_IOS + return "iOS " + nsStringToBeast ([[UIDevice currentDevice] systemVersion]); + #else + return "Mac OSX " + getOSXVersion(); + #endif +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if BEAST_IOS + return false; + #elif BEAST_64BIT + return true; + #else + return getOperatingSystemType() >= MacOSX_10_6; + #endif +} + +int SystemStats::getMemorySizeInMegabytes() +{ + uint64 mem = 0; + size_t memSize = sizeof (mem); + int mib[] = { CTL_HW, HW_MEMSIZE }; + sysctl (mib, 2, &mem, &memSize, 0, 0); + return (int) (mem / (1024 * 1024)); +} + +String SystemStats::getCpuVendor() +{ + #if BEAST_INTEL && ! BEAST_NO_INLINE_ASM + uint32 dummy = 0; + uint32 vendor[4] = { 0 }; + + SystemStatsHelpers::doCPUID (dummy, vendor[0], vendor[2], vendor[1], 0); + + return String (reinterpret_cast (vendor), 12); + #else + return String::empty; + #endif +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + uint64 speedHz = 0; + size_t speedSize = sizeof (speedHz); + int mib[] = { CTL_HW, HW_CPU_FREQ }; + sysctl (mib, 2, &speedHz, &speedSize, 0, 0); + + #if BEAST_BIG_ENDIAN + if (speedSize == 4) + speedHz >>= 32; + #endif + + return (int) (speedHz / 1000000); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + return nsStringToBeast (NSUserName()); +} + +String SystemStats::getFullUserName() +{ + return nsStringToBeast (NSFullUserName()); +} + +String SystemStats::getComputerName() +{ + char name [256] = { 0 }; + if (gethostname (name, sizeof (name) - 1) == 0) + return String (name).upToLastOccurrenceOf (".local", false, true); + + return String::empty; +} + +static String getLocaleValue (CFStringRef key) +{ + CFLocaleRef cfLocale = CFLocaleCopyCurrent(); + const String result (String::fromCFString ((CFStringRef) CFLocaleGetValue (cfLocale, key))); + CFRelease (cfLocale); + return result; +} + +String SystemStats::getUserLanguage() { return getLocaleValue (kCFLocaleLanguageCode); } +String SystemStats::getUserRegion() { return getLocaleValue (kCFLocaleCountryCode); } + +String SystemStats::getDisplayLanguage() +{ + CFArrayRef cfPrefLangs = CFLocaleCopyPreferredLanguages(); + const String result (String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (cfPrefLangs, 0))); + CFRelease (cfPrefLangs); + return result; +} + +//============================================================================== +class HiResCounterHandler +{ +public: + HiResCounterHandler() + { + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + + if (timebase.numer % 1000000 == 0) + { + numerator = timebase.numer / 1000000; + denominator = timebase.denom; + } + else + { + numerator = timebase.numer; + denominator = timebase.denom * (uint64) 1000000; + } + + highResTimerFrequency = (timebase.denom * (uint64) 1000000000) / timebase.numer; + highResTimerToMillisecRatio = numerator / (double) denominator; + } + + inline uint32 millisecondsSinceStartup() const noexcept + { + return (uint32) ((mach_absolute_time() * numerator) / denominator); + } + + inline double getMillisecondCounterHiRes() const noexcept + { + return mach_absolute_time() * highResTimerToMillisecRatio; + } + + int64 highResTimerFrequency; + +private: + uint64 numerator, denominator; + double highResTimerToMillisecRatio; +}; + +static HiResCounterHandler hiResCounterHandler; + +uint32 beast_millisecondsSinceStartup() noexcept { return hiResCounterHandler.millisecondsSinceStartup(); } +double Time::getMillisecondCounterHiRes() noexcept { return hiResCounterHandler.getMillisecondCounterHiRes(); } +int64 Time::getHighResolutionTicksPerSecond() noexcept { return hiResCounterHandler.highResTimerFrequency; } +int64 Time::getHighResolutionTicks() noexcept { return (int64) mach_absolute_time(); } + +bool Time::setSystemTimeToThisTime() const +{ + jassertfalse; + return false; +} + +//============================================================================== +int SystemStats::getPageSize() +{ + return (int) NSPageSize(); +} diff --git a/modules/beast_core/native/beast_mac_Threads.mm b/modules/beast_core/native/beast_mac_Threads.mm new file mode 100644 index 0000000000..340b3c3c11 --- /dev/null +++ b/modules/beast_core/native/beast_mac_Threads.mm @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in beast_posix_SharedCode.h! +*/ + +//============================================================================== +bool Process::isForegroundProcess() +{ + #if BEAST_MAC + return [NSApp isActive]; + #else + return true; // xxx change this if more than one app is ever possible on iOS! + #endif +} + +void Process::makeForegroundProcess() +{ + #if BEAST_MAC + [NSApp activateIgnoringOtherApps: YES]; + #endif +} + +void Process::raisePrivilege() +{ + jassertfalse; +} + +void Process::lowerPrivilege() +{ + jassertfalse; +} + +void Process::terminate() +{ + exit (0); +} + +void Process::setPriority (ProcessPriority) +{ + // xxx +} + +//============================================================================== +BEAST_API bool BEAST_CALLTYPE beast_isRunningUnderDebugger() +{ + static char testResult = 0; + + if (testResult == 0) + { + struct kinfo_proc info; + int m[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + size_t sz = sizeof (info); + sysctl (m, 4, &info, &sz, 0, 0); + testResult = ((info.kp_proc.p_flag & P_TRACED) != 0) ? 1 : -1; + } + + return testResult > 0; +} + +BEAST_API bool BEAST_CALLTYPE Process::isRunningUnderDebugger() +{ + return beast_isRunningUnderDebugger(); +} diff --git a/modules/beast_core/native/beast_osx_ObjCHelpers.h b/modules/beast_core/native/beast_osx_ObjCHelpers.h new file mode 100644 index 0000000000..d565e9d845 --- /dev/null +++ b/modules/beast_core/native/beast_osx_ObjCHelpers.h @@ -0,0 +1,153 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_OSX_OBJCHELPERS_BEASTHEADER +#define BEAST_OSX_OBJCHELPERS_BEASTHEADER + + +/* This file contains a few helper functions that are used internally but which + need to be kept away from the public headers because they use obj-C symbols. +*/ +namespace +{ + //============================================================================== + static inline String nsStringToBeast (NSString* s) + { + return CharPointer_UTF8 ([s UTF8String]); + } + + static inline NSString* beastStringToNS (const String& s) + { + return [NSString stringWithUTF8String: s.toUTF8()]; + } + + static inline NSString* nsStringLiteral (const char* const s) noexcept + { + return [NSString stringWithUTF8String: s]; + } + + static inline NSString* nsEmptyString() noexcept + { + return [NSString string]; + } +} + +//============================================================================== +template +struct NSObjectRetainer +{ + inline NSObjectRetainer (ObjectType* o) : object (o) { [object retain]; } + inline ~NSObjectRetainer() { [object release]; } + + ObjectType* object; +}; + +//============================================================================== +template +struct ObjCClass +{ + ObjCClass (const char* nameRoot) + : cls (objc_allocateClassPair ([SuperclassType class], getRandomisedName (nameRoot).toUTF8(), 0)) + { + } + + ~ObjCClass() + { + objc_disposeClassPair (cls); + } + + void registerClass() + { + objc_registerClassPair (cls); + } + + SuperclassType* createInstance() const + { + return class_createInstance (cls, 0); + } + + template + void addIvar (const char* name) + { + BOOL b = class_addIvar (cls, name, sizeof (Type), (uint8_t) rint (log2 (sizeof (Type))), @encode (Type)); + bassert (b); (void) b; + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* signature) + { + BOOL b = class_addMethod (cls, selector, (IMP) callbackFn, signature); + bassert (b); (void) b; + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2) + { + addMethod (selector, callbackFn, (String (sig1) + sig2).toUTF8()); + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3) + { + addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3).toUTF8()); + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3, const char* sig4) + { + addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3 + sig4).toUTF8()); + } + + void addProtocol (Protocol* protocol) + { + BOOL b = class_addProtocol (cls, protocol); + bassert (b); (void) b; + } + + static id sendSuperclassMessage (id self, SEL selector) + { + objc_super s = { self, [SuperclassType class] }; + return objc_msgSendSuper (&s, selector); + } + + template + static Type getIvar (id self, const char* name) + { + void* v = nullptr; + object_getInstanceVariable (self, name, &v); + return static_cast (v); + } + + Class cls; + +private: + static String getRandomisedName (const char* root) + { + return root + String::toHexString (beast::Random::getSystemRandom().nextInt64()); + } + + BEAST_DECLARE_NON_COPYABLE (ObjCClass) +}; + + +#endif // BEAST_OSX_OBJCHELPERS_BEASTHEADER diff --git a/modules/beast_core/native/beast_posix_NamedPipe.cpp b/modules/beast_core/native/beast_posix_NamedPipe.cpp new file mode 100644 index 0000000000..ed5a6b1f0a --- /dev/null +++ b/modules/beast_core/native/beast_posix_NamedPipe.cpp @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +class NamedPipe::Pimpl +{ +public: + Pimpl (const String& pipePath, bool createPipe) + : pipeInName (pipePath + "_in"), + pipeOutName (pipePath + "_out"), + pipeIn (-1), pipeOut (-1), + createdPipe (createPipe), + stopReadOperation (false) + { + signal (SIGPIPE, signalHandler); + siginterrupt (SIGPIPE, 1); + } + + ~Pimpl() + { + if (pipeIn != -1) ::close (pipeIn); + if (pipeOut != -1) ::close (pipeOut); + + if (createdPipe) + { + unlink (pipeInName.toUTF8()); + unlink (pipeOutName.toUTF8()); + } + } + + int read (char* destBuffer, int maxBytesToRead, int timeOutMilliseconds) + { + const uint32 timeoutEnd = getTimeoutEnd (timeOutMilliseconds); + + if (pipeIn == -1) + { + pipeIn = openPipe (createdPipe ? pipeInName : pipeOutName, O_RDWR | O_NONBLOCK, timeoutEnd); + + if (pipeIn == -1) + return -1; + } + + int bytesRead = 0; + + while (bytesRead < maxBytesToRead) + { + const int bytesThisTime = maxBytesToRead - bytesRead; + const int numRead = (int) ::read (pipeIn, destBuffer, (size_t) bytesThisTime); + + if (numRead <= 0) + { + if (errno != EWOULDBLOCK || stopReadOperation || hasExpired (timeoutEnd)) + return -1; + + const int maxWaitingTime = 30; + waitForInput (pipeIn, timeoutEnd == 0 ? maxWaitingTime + : bmin (maxWaitingTime, + (int) (timeoutEnd - Time::getMillisecondCounter()))); + continue; + } + + bytesRead += numRead; + destBuffer += numRead; + } + + return bytesRead; + } + + int write (const char* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) + { + const uint32 timeoutEnd = getTimeoutEnd (timeOutMilliseconds); + + if (pipeOut == -1) + { + pipeOut = openPipe (createdPipe ? pipeOutName : pipeInName, O_WRONLY, timeoutEnd); + + if (pipeOut == -1) + return -1; + } + + int bytesWritten = 0; + + while (bytesWritten < numBytesToWrite && ! hasExpired (timeoutEnd)) + { + const int bytesThisTime = numBytesToWrite - bytesWritten; + const int numWritten = (int) ::write (pipeOut, sourceBuffer, (size_t) bytesThisTime); + + if (numWritten <= 0) + return -1; + + bytesWritten += numWritten; + sourceBuffer += numWritten; + } + + return bytesWritten; + } + + bool createFifos() const + { + return (mkfifo (pipeInName .toUTF8(), 0666) == 0 || errno == EEXIST) + && (mkfifo (pipeOutName.toUTF8(), 0666) == 0 || errno == EEXIST); + } + + const String pipeInName, pipeOutName; + int pipeIn, pipeOut; + + const bool createdPipe; + bool stopReadOperation; + +private: + static void signalHandler (int) {} + + static uint32 getTimeoutEnd (const int timeOutMilliseconds) + { + return timeOutMilliseconds >= 0 ? Time::getMillisecondCounter() + (uint32) timeOutMilliseconds : 0; + } + + static bool hasExpired (const uint32 timeoutEnd) + { + return timeoutEnd != 0 && Time::getMillisecondCounter() >= timeoutEnd; + } + + int openPipe (const String& name, int flags, const uint32 timeoutEnd) + { + for (;;) + { + const int p = ::open (name.toUTF8(), flags); + + if (p != -1 || hasExpired (timeoutEnd) || stopReadOperation) + return p; + + Thread::sleep (2); + } + } + + static void waitForInput (const int handle, const int timeoutMsecs) noexcept + { + struct timeval timeout; + timeout.tv_sec = timeoutMsecs / 1000; + timeout.tv_usec = (timeoutMsecs % 1000) * 1000; + + fd_set rset; + FD_ZERO (&rset); + FD_SET (handle, &rset); + + select (handle + 1, &rset, nullptr, 0, &timeout); + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +void NamedPipe::close() +{ + if (pimpl != nullptr) + { + pimpl->stopReadOperation = true; + + char buffer[1] = { 0 }; + ssize_t done = ::write (pimpl->pipeIn, buffer, 1); + (void) done; + + ScopedWriteLock sl (lock); + pimpl = nullptr; + } +} + +bool NamedPipe::openInternal (const String& pipeName, const bool createPipe) +{ + #if BEAST_IOS + pimpl = new Pimpl (File::getSpecialLocation (File::tempDirectory) + .getChildFile (File::createLegalFileName (pipeName)).getFullPathName(), createPipe); + #else + pimpl = new Pimpl ("/tmp/" + File::createLegalFileName (pipeName), createPipe); + #endif + + if (createPipe && ! pimpl->createFifos()) + { + pimpl = nullptr; + return false; + } + + return true; +} + +int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->read (static_cast (destBuffer), maxBytesToRead, timeOutMilliseconds) : -1; +} + +int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->write (static_cast (sourceBuffer), numBytesToWrite, timeOutMilliseconds) : -1; +} diff --git a/modules/beast_core/native/beast_posix_SharedCode.h b/modules/beast_core/native/beast_posix_SharedCode.h new file mode 100644 index 0000000000..9d6182f721 --- /dev/null +++ b/modules/beast_core/native/beast_posix_SharedCode.h @@ -0,0 +1,1251 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +CriticalSection::CriticalSection() noexcept +{ + pthread_mutexattr_t atts; + pthread_mutexattr_init (&atts); + pthread_mutexattr_settype (&atts, PTHREAD_MUTEX_RECURSIVE); + #if ! BEAST_ANDROID + pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT); + #endif + pthread_mutex_init (&internal, &atts); +} + +CriticalSection::~CriticalSection() noexcept +{ + pthread_mutex_destroy (&internal); +} + +void CriticalSection::enter() const noexcept +{ + pthread_mutex_lock (&internal); +} + +bool CriticalSection::tryEnter() const noexcept +{ + return pthread_mutex_trylock (&internal) == 0; +} + +void CriticalSection::exit() const noexcept +{ + pthread_mutex_unlock (&internal); +} + + +//============================================================================== +WaitableEvent::WaitableEvent (const bool useManualReset) noexcept + : triggered (false), manualReset (useManualReset) +{ + pthread_cond_init (&condition, 0); + + pthread_mutexattr_t atts; + pthread_mutexattr_init (&atts); + #if ! BEAST_ANDROID + pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT); + #endif + pthread_mutex_init (&mutex, &atts); +} + +WaitableEvent::~WaitableEvent() noexcept +{ + pthread_cond_destroy (&condition); + pthread_mutex_destroy (&mutex); +} + +bool WaitableEvent::wait (const int timeOutMillisecs) const noexcept +{ + pthread_mutex_lock (&mutex); + + if (! triggered) + { + if (timeOutMillisecs < 0) + { + do + { + pthread_cond_wait (&condition, &mutex); + } + while (! triggered); + } + else + { + struct timeval now; + gettimeofday (&now, 0); + + struct timespec time; + time.tv_sec = now.tv_sec + (timeOutMillisecs / 1000); + time.tv_nsec = (now.tv_usec + ((timeOutMillisecs % 1000) * 1000)) * 1000; + + if (time.tv_nsec >= 1000000000) + { + time.tv_nsec -= 1000000000; + time.tv_sec++; + } + + do + { + if (pthread_cond_timedwait (&condition, &mutex, &time) == ETIMEDOUT) + { + pthread_mutex_unlock (&mutex); + return false; + } + } + while (! triggered); + } + } + + if (! manualReset) + triggered = false; + + pthread_mutex_unlock (&mutex); + return true; +} + +void WaitableEvent::signal() const noexcept +{ + pthread_mutex_lock (&mutex); + triggered = true; + pthread_cond_broadcast (&condition); + pthread_mutex_unlock (&mutex); +} + +void WaitableEvent::reset() const noexcept +{ + pthread_mutex_lock (&mutex); + triggered = false; + pthread_mutex_unlock (&mutex); +} + +//============================================================================== +void BEAST_CALLTYPE Thread::sleep (int millisecs) +{ + struct timespec time; + time.tv_sec = millisecs / 1000; + time.tv_nsec = (millisecs % 1000) * 1000000; + nanosleep (&time, nullptr); +} + + +//============================================================================== +const beast_wchar File::separator = '/'; +const String File::separatorString ("/"); + +//============================================================================== +File File::getCurrentWorkingDirectory() +{ + HeapBlock heapBuffer; + + char localBuffer [1024]; + char* cwd = getcwd (localBuffer, sizeof (localBuffer) - 1); + size_t bufferSize = 4096; + + while (cwd == nullptr && errno == ERANGE) + { + heapBuffer.malloc (bufferSize); + cwd = getcwd (heapBuffer, bufferSize - 1); + bufferSize += 1024; + } + + return File (CharPointer_UTF8 (cwd)); +} + +bool File::setAsCurrentWorkingDirectory() const +{ + return chdir (getFullPathName().toUTF8()) == 0; +} + +//============================================================================== +namespace +{ + #if BEAST_LINUX || (BEAST_IOS && ! __DARWIN_ONLY_64_BIT_INO_T) // (this iOS stuff is to avoid a simulator bug) + typedef struct stat64 beast_statStruct; + #define BEAST_STAT stat64 + #else + typedef struct stat beast_statStruct; + #define BEAST_STAT stat + #endif + + bool beast_stat (const String& fileName, beast_statStruct& info) + { + return fileName.isNotEmpty() + && BEAST_STAT (fileName.toUTF8(), &info) == 0; + } + + // if this file doesn't exist, find a parent of it that does.. + bool beast_doStatFS (File f, struct statfs& result) + { + for (int i = 5; --i >= 0;) + { + if (f.exists()) + break; + + f = f.getParentDirectory(); + } + + return statfs (f.getFullPathName().toUTF8(), &result) == 0; + } + + void updateStatInfoForFile (const String& path, bool* const isDir, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + if (isDir != nullptr || fileSize != nullptr || modTime != nullptr || creationTime != nullptr) + { + beast_statStruct info; + const bool statOk = beast_stat (path, info); + + if (isDir != nullptr) *isDir = statOk && ((info.st_mode & S_IFDIR) != 0); + if (fileSize != nullptr) *fileSize = statOk ? info.st_size : 0; + if (modTime != nullptr) *modTime = Time (statOk ? (int64) info.st_mtime * 1000 : 0); + if (creationTime != nullptr) *creationTime = Time (statOk ? (int64) info.st_ctime * 1000 : 0); + } + + if (isReadOnly != nullptr) + *isReadOnly = access (path.toUTF8(), W_OK) != 0; + } + + Result getResultForErrno() + { + return Result::fail (String (strerror (errno))); + } + + Result getResultForReturnValue (int value) + { + return value == -1 ? getResultForErrno() : Result::ok(); + } + + int getFD (void* handle) noexcept { return (int) (pointer_sized_int) handle; } + void* fdToVoidPointer (int fd) noexcept { return (void*) (pointer_sized_int) fd; } +} + +bool File::isDirectory() const +{ + beast_statStruct info; + + return fullPath.isEmpty() + || (beast_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0)); +} + +bool File::exists() const +{ + return fullPath.isNotEmpty() + && access (fullPath.toUTF8(), F_OK) == 0; +} + +bool File::existsAsFile() const +{ + return exists() && ! isDirectory(); +} + +int64 File::getSize() const +{ + beast_statStruct info; + return beast_stat (fullPath, info) ? info.st_size : 0; +} + +//============================================================================== +bool File::hasWriteAccess() const +{ + if (exists()) + return access (fullPath.toUTF8(), W_OK) == 0; + + if ((! isDirectory()) && fullPath.containsChar (separator)) + return getParentDirectory().hasWriteAccess(); + + return false; +} + +bool File::setFileReadOnlyInternal (const bool shouldBeReadOnly) const +{ + beast_statStruct info; + if (! beast_stat (fullPath, info)) + return false; + + info.st_mode &= 0777; // Just permissions + + if (shouldBeReadOnly) + info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + else + // Give everybody write permission? + info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + return chmod (fullPath.toUTF8(), info.st_mode) == 0; +} + +void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const +{ + modificationTime = 0; + accessTime = 0; + creationTime = 0; + + beast_statStruct info; + if (beast_stat (fullPath, info)) + { + modificationTime = (int64) info.st_mtime * 1000; + accessTime = (int64) info.st_atime * 1000; + creationTime = (int64) info.st_ctime * 1000; + } +} + +bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 /*creationTime*/) const +{ + beast_statStruct info; + + if ((modificationTime != 0 || accessTime != 0) && beast_stat (fullPath, info)) + { + struct utimbuf times; + times.actime = accessTime != 0 ? (time_t) (accessTime / 1000) : info.st_atime; + times.modtime = modificationTime != 0 ? (time_t) (modificationTime / 1000) : info.st_mtime; + + return utime (fullPath.toUTF8(), ×) == 0; + } + + return false; +} + +bool File::deleteFile() const +{ + if (! exists()) + return true; + + if (isDirectory()) + return rmdir (fullPath.toUTF8()) == 0; + + return remove (fullPath.toUTF8()) == 0; +} + +bool File::moveInternal (const File& dest) const +{ + if (rename (fullPath.toUTF8(), dest.getFullPathName().toUTF8()) == 0) + return true; + + if (hasWriteAccess() && copyInternal (dest)) + { + if (deleteFile()) + return true; + + dest.deleteFile(); + } + + return false; +} + +Result File::createDirectoryInternal (const String& fileName) const +{ + return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777)); +} + +//===================================================================== +int64 beast_fileSetPosition (void* handle, int64 pos) +{ + if (handle != 0 && lseek (getFD (handle), pos, SEEK_SET) == pos) + return pos; + + return -1; +} + +void FileInputStream::openHandle() +{ + const int f = open (file.getFullPathName().toUTF8(), O_RDONLY, 00644); + + if (f != -1) + fileHandle = fdToVoidPointer (f); + else + status = getResultForErrno(); +} + +void FileInputStream::closeHandle() +{ + if (fileHandle != 0) + { + close (getFD (fileHandle)); + fileHandle = 0; + } +} + +size_t FileInputStream::readInternal (void* const buffer, const size_t numBytes) +{ + ssize_t result = 0; + + if (fileHandle != 0) + { + result = ::read (getFD (fileHandle), buffer, numBytes); + + if (result < 0) + { + status = getResultForErrno(); + result = 0; + } + } + + return (size_t) result; +} + +//============================================================================== +void FileOutputStream::openHandle() +{ + if (file.exists()) + { + const int f = open (file.getFullPathName().toUTF8(), O_RDWR, 00644); + + if (f != -1) + { + currentPosition = lseek (f, 0, SEEK_END); + + if (currentPosition >= 0) + { + fileHandle = fdToVoidPointer (f); + } + else + { + status = getResultForErrno(); + close (f); + } + } + else + { + status = getResultForErrno(); + } + } + else + { + const int f = open (file.getFullPathName().toUTF8(), O_RDWR + O_CREAT, 00644); + + if (f != -1) + fileHandle = fdToVoidPointer (f); + else + status = getResultForErrno(); + } +} + +void FileOutputStream::closeHandle() +{ + if (fileHandle != 0) + { + close (getFD (fileHandle)); + fileHandle = 0; + } +} + +ssize_t FileOutputStream::writeInternal (const void* const data, const size_t numBytes) +{ + ssize_t result = 0; + + if (fileHandle != 0) + { + result = ::write (getFD (fileHandle), data, numBytes); + + if (result == -1) + status = getResultForErrno(); + } + + return result; +} + +void FileOutputStream::flushInternal() +{ + if (fileHandle != 0) + { + if (fsync (getFD (fileHandle)) == -1) + status = getResultForErrno(); + + #if BEAST_ANDROID + // This stuff tells the OS to asynchronously update the metadata + // that the OS has cached aboud the file - this metadata is used + // when the device is acting as a USB drive, and unless it's explicitly + // refreshed, it'll get out of step with the real file. + const LocalRef t (javaString (file.getFullPathName())); + android.activity.callVoidMethod (BeastAppActivity.scanFile, t.get()); + #endif + } +} + +Result FileOutputStream::truncate() +{ + if (fileHandle == 0) + return status; + + flush(); + return getResultForReturnValue (ftruncate (getFD (fileHandle), (off_t) currentPosition)); +} + +//============================================================================== +String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue) +{ + if (const char* s = ::getenv (name.toUTF8())) + return String::fromUTF8 (s); + + return defaultValue; +} + +//============================================================================== +void MemoryMappedFile::openInternal (const File& file, AccessMode mode) +{ + bassert (mode == readOnly || mode == readWrite); + + if (range.getStart() > 0) + { + const long pageSize = sysconf (_SC_PAGE_SIZE); + range.setStart (range.getStart() - (range.getStart() % pageSize)); + } + + fileHandle = open (file.getFullPathName().toUTF8(), + mode == readWrite ? (O_CREAT + O_RDWR) : O_RDONLY, 00644); + + if (fileHandle != -1) + { + void* m = mmap (0, (size_t) range.getLength(), + mode == readWrite ? (PROT_READ | PROT_WRITE) : PROT_READ, + MAP_SHARED, fileHandle, + (off_t) range.getStart()); + + if (m != MAP_FAILED) + { + address = m; + madvise (m, (size_t) range.getLength(), MADV_SEQUENTIAL); + } + else + { + range = Range(); + } + } +} + +MemoryMappedFile::~MemoryMappedFile() +{ + if (address != nullptr) + munmap (address, (size_t) range.getLength()); + + if (fileHandle != 0) + close (fileHandle); +} + +//============================================================================== +#if BEAST_PROBEASTR_LIVE_BUILD +extern "C" const char* beast_getCurrentExecutablePath(); +#endif + +File beast_getExecutableFile(); +File beast_getExecutableFile() +{ + #if BEAST_PROBEASTR_LIVE_BUILD + return File (beast_getCurrentExecutablePath()); + #elif BEAST_ANDROID + return File (android.appFile); + #else + struct DLAddrReader + { + static String getFilename() + { + Dl_info exeInfo; + dladdr ((void*) beast_getExecutableFile, &exeInfo); + return CharPointer_UTF8 (exeInfo.dli_fname); + } + }; + + static String filename (DLAddrReader::getFilename()); + return File::getCurrentWorkingDirectory().getChildFile (filename); + #endif +} + +//============================================================================== +int64 File::getBytesFreeOnVolume() const +{ + struct statfs buf; + if (beast_doStatFS (*this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_bavail; // Note: this returns space available to non-super user + + return 0; +} + +int64 File::getVolumeTotalSize() const +{ + struct statfs buf; + if (beast_doStatFS (*this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_blocks; + + return 0; +} + +String File::getVolumeLabel() const +{ + #if BEAST_MAC + struct VolAttrBuf + { + u_int32_t length; + attrreference_t mountPointRef; + char mountPointSpace [MAXPATHLEN]; + } attrBuf; + + struct attrlist attrList; + zerostruct (attrList); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.volattr = ATTR_VOL_INFO | ATTR_VOL_NAME; + + File f (*this); + + for (;;) + { + if (getattrlist (f.getFullPathName().toUTF8(), &attrList, &attrBuf, sizeof (attrBuf), 0) == 0) + return String::fromUTF8 (((const char*) &attrBuf.mountPointRef) + attrBuf.mountPointRef.attr_dataoffset, + (int) attrBuf.mountPointRef.attr_length); + + const File parent (f.getParentDirectory()); + + if (f == parent) + break; + + f = parent; + } + #endif + + return String::empty; +} + +int File::getVolumeSerialNumber() const +{ + int result = 0; +/* int fd = open (getFullPathName().toUTF8(), O_RDONLY | O_NONBLOCK); + + char info [512]; + + #ifndef HDIO_GET_IDENTITY + #define HDIO_GET_IDENTITY 0x030d + #endif + + if (ioctl (fd, HDIO_GET_IDENTITY, info) == 0) + { + DBG (String (info + 20, 20)); + result = String (info + 20, 20).trim().getIntValue(); + } + + close (fd);*/ + return result; +} + +//============================================================================== +void beast_runSystemCommand (const String&); +void beast_runSystemCommand (const String& command) +{ + int result = system (command.toUTF8()); + (void) result; +} + +String beast_getOutputFromCommand (const String&); +String beast_getOutputFromCommand (const String& command) +{ + // slight bodge here, as we just pipe the output into a temp file and read it... + const File tempFile (File::getSpecialLocation (File::tempDirectory) + .getNonexistentChildFile (String::toHexString (Random::getSystemRandom().nextInt()), ".tmp", false)); + + beast_runSystemCommand (command + " > " + tempFile.getFullPathName()); + + String result (tempFile.loadFileAsString()); + tempFile.deleteFile(); + return result; +} + + +//============================================================================== +#if BEAST_IOS +class InterProcessLock::Pimpl +{ +public: + Pimpl (const String&, int) + : handle (1), refCount (1) // On iOS just fake success.. + { + } + + int handle, refCount; +}; + +#else + +class InterProcessLock::Pimpl +{ +public: + Pimpl (const String& lockName, const int timeOutMillisecs) + : handle (0), refCount (1) + { + #if BEAST_MAC + if (! createLockFile (File ("~/Library/Caches/com.beast.locks").getChildFile (lockName), timeOutMillisecs)) + // Fallback if the user's home folder is on a network drive with no ability to lock.. + createLockFile (File ("/tmp/com.beast.locks").getChildFile (lockName), timeOutMillisecs); + + #else + File tempFolder ("/var/tmp"); + if (! tempFolder.isDirectory()) + tempFolder = "/tmp"; + + createLockFile (tempFolder.getChildFile (lockName), timeOutMillisecs); + #endif + } + + ~Pimpl() + { + closeFile(); + } + + bool createLockFile (const File& file, const int timeOutMillisecs) + { + file.create(); + handle = open (file.getFullPathName().toUTF8(), O_RDWR); + + if (handle != 0) + { + struct flock fl; + zerostruct (fl); + + fl.l_whence = SEEK_SET; + fl.l_type = F_WRLCK; + + const int64 endTime = Time::currentTimeMillis() + timeOutMillisecs; + + for (;;) + { + const int result = fcntl (handle, F_SETLK, &fl); + + if (result >= 0) + return true; + + const int error = errno; + + if (error != EINTR) + { + if (error == EBADF || error == ENOTSUP) + return false; + + if (timeOutMillisecs == 0 + || (timeOutMillisecs > 0 && Time::currentTimeMillis() >= endTime)) + break; + + Thread::sleep (10); + } + } + } + + closeFile(); + return true; // only false if there's a file system error. Failure to lock still returns true. + } + + void closeFile() + { + if (handle != 0) + { + struct flock fl; + zerostruct (fl); + + fl.l_whence = SEEK_SET; + fl.l_type = F_UNLCK; + + while (! (fcntl (handle, F_SETLKW, &fl) >= 0 || errno != EINTR)) + {} + + close (handle); + handle = 0; + } + } + + int handle, refCount; +}; +#endif + +InterProcessLock::InterProcessLock (const String& nm) : name (nm) +{ +} + +InterProcessLock::~InterProcessLock() +{ +} + +bool InterProcessLock::enter (const int timeOutMillisecs) +{ + const ScopedLock sl (lock); + + if (pimpl == nullptr) + { + pimpl = new Pimpl (name, timeOutMillisecs); + + if (pimpl->handle == 0) + pimpl = nullptr; + } + else + { + pimpl->refCount++; + } + + return pimpl != nullptr; +} + +void InterProcessLock::exit() +{ + const ScopedLock sl (lock); + + // Trying to release the lock too many times! + bassert (pimpl != nullptr); + + if (pimpl != nullptr && --(pimpl->refCount) == 0) + pimpl = nullptr; +} + +//============================================================================== +void BEAST_API beast_threadEntryPoint (void*); + +extern "C" void* threadEntryProc (void*); +extern "C" void* threadEntryProc (void* userData) +{ + BEAST_AUTORELEASEPOOL + { + #if BEAST_ANDROID + struct AndroidThreadScope + { + AndroidThreadScope() { threadLocalJNIEnvHolder.attach(); } + ~AndroidThreadScope() { threadLocalJNIEnvHolder.detach(); } + }; + + const AndroidThreadScope androidEnv; + #endif + + beast_threadEntryPoint (userData); + } + + return nullptr; +} + +void Thread::launchThread() +{ + threadHandle = 0; + pthread_t handle = 0; + + if (pthread_create (&handle, 0, threadEntryProc, this) == 0) + { + pthread_detach (handle); + threadHandle = (void*) handle; + threadId = (ThreadID) threadHandle; + } +} + +void Thread::closeThreadHandle() +{ + threadId = 0; + threadHandle = 0; +} + +void Thread::killThread() +{ + if (threadHandle != 0) + { + #if BEAST_ANDROID + jassertfalse; // pthread_cancel not available! + #else + pthread_cancel ((pthread_t) threadHandle); + #endif + } +} + +void Thread::setCurrentThreadName (const String& name) +{ + #if BEAST_IOS || (BEAST_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + BEAST_AUTORELEASEPOOL + { + [[NSThread currentThread] setName: beastStringToNS (name)]; + } + #elif BEAST_LINUX + pthread_setname_np (pthread_self(), name.toRawUTF8()); + #endif +} + +bool Thread::setThreadPriority (void* handle, int priority) +{ + struct sched_param param; + int policy; + priority = blimit (0, 10, priority); + + if (handle == 0) + handle = (void*) pthread_self(); + + if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) + return false; + + policy = priority == 0 ? SCHED_OTHER : SCHED_RR; + + const int minPriority = sched_get_priority_min (policy); + const int maxPriority = sched_get_priority_max (policy); + + param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; + return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; +} + +Thread::ThreadID Thread::getCurrentThreadId() +{ + return (ThreadID) pthread_self(); +} + +void Thread::yield() +{ + sched_yield(); +} + +//============================================================================== +/* Remove this macro if you're having problems compiling the cpu affinity + calls (the API for these has changed about quite a bit in various Linux + versions, and a lot of distros seem to ship with obsolete versions) +*/ +#if defined (CPU_ISSET) && ! defined (SUPPORT_AFFINITIES) + #define SUPPORT_AFFINITIES 1 +#endif + +void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) +{ + #if SUPPORT_AFFINITIES + cpu_set_t affinity; + CPU_ZERO (&affinity); + + for (int i = 0; i < 32; ++i) + if ((affinityMask & (1 << i)) != 0) + CPU_SET (i, &affinity); + + /* + N.B. If this line causes a compile error, then you've probably not got the latest + version of glibc installed. + + If you don't want to update your copy of glibc and don't care about cpu affinities, + then you can just disable all this stuff by setting the SUPPORT_AFFINITIES macro to 0. + */ + sched_setaffinity (getpid(), sizeof (cpu_set_t), &affinity); + sched_yield(); + + #else + /* affinities aren't supported because either the appropriate header files weren't found, + or the SUPPORT_AFFINITIES macro was turned off + */ + jassertfalse; + (void) affinityMask; + #endif +} + +//============================================================================== +bool DynamicLibrary::open (const String& name) +{ + close(); + handle = dlopen (name.isEmpty() ? nullptr : name.toUTF8().getAddress(), RTLD_LOCAL | RTLD_NOW); + return handle != nullptr; +} + +void DynamicLibrary::close() +{ + if (handle != nullptr) + { + dlclose (handle); + handle = nullptr; + } +} + +void* DynamicLibrary::getFunction (const String& functionName) noexcept +{ + return handle != nullptr ? dlsym (handle, functionName.toUTF8()) : nullptr; +} + + + +//============================================================================== +class ChildProcess::ActiveProcess +{ +public: + ActiveProcess (const StringArray& arguments) + : childPID (0), pipeHandle (0), readHandle (0) + { + int pipeHandles[2] = { 0 }; + + if (pipe (pipeHandles) == 0) + { + const pid_t result = fork(); + + if (result < 0) + { + close (pipeHandles[0]); + close (pipeHandles[1]); + } + else if (result == 0) + { + // we're the child process.. + close (pipeHandles[0]); // close the read handle + dup2 (pipeHandles[1], 1); // turns the pipe into stdout + dup2 (pipeHandles[1], 2); // + stderr + close (pipeHandles[1]); + + Array argv; + for (int i = 0; i < arguments.size(); ++i) + if (arguments[i].isNotEmpty()) + argv.add (arguments[i].toUTF8().getAddress()); + + argv.add (nullptr); + + execvp (argv[0], argv.getRawDataPointer()); + exit (-1); + } + else + { + // we're the parent process.. + childPID = result; + pipeHandle = pipeHandles[0]; + close (pipeHandles[1]); // close the write handle + } + } + } + + ~ActiveProcess() + { + if (readHandle != 0) + fclose (readHandle); + + if (pipeHandle != 0) + close (pipeHandle); + } + + bool isRunning() const + { + if (childPID != 0) + { + int childState; + const int pid = waitpid (childPID, &childState, WNOHANG); + return pid == 0 || ! (WIFEXITED (childState) || WIFSIGNALED (childState)); + } + + return false; + } + + int read (void* const dest, const int numBytes) + { + bassert (dest != nullptr); + + #ifdef fdopen + #error // the zlib headers define this function as NULL! + #endif + + if (readHandle == 0 && childPID != 0) + readHandle = fdopen (pipeHandle, "r"); + + if (readHandle != 0) + return (int) fread (dest, 1, (size_t) numBytes, readHandle); + + return 0; + } + + bool killProcess() const + { + return ::kill (childPID, SIGKILL) == 0; + } + + int childPID; + +private: + int pipeHandle; + FILE* readHandle; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveProcess) +}; + +bool ChildProcess::start (const String& command) +{ + return start (StringArray::fromTokens (command, true)); +} + +bool ChildProcess::start (const StringArray& args) +{ + if (args.size() == 0) + return false; + + activeProcess = new ActiveProcess (args); + + if (activeProcess->childPID == 0) + activeProcess = nullptr; + + return activeProcess != nullptr; +} + +bool ChildProcess::isRunning() const +{ + return activeProcess != nullptr && activeProcess->isRunning(); +} + +int ChildProcess::readProcessOutput (void* dest, int numBytes) +{ + return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; +} + +bool ChildProcess::kill() +{ + return activeProcess == nullptr || activeProcess->killProcess(); +} + +//============================================================================== +struct HighResolutionTimer::Pimpl +{ + Pimpl (HighResolutionTimer& t) : owner (t), thread (0), shouldStop (false) + { + } + + ~Pimpl() + { + bassert (thread == 0); + } + + void start (int newPeriod) + { + periodMs = newPeriod; + + if (thread == 0) + { + shouldStop = false; + + if (pthread_create (&thread, nullptr, timerThread, this) == 0) + setThreadToRealtime (thread, newPeriod); + else + jassertfalse; + } + } + + void stop() + { + if (thread != 0) + { + shouldStop = true; + + while (thread != 0 && thread != pthread_self()) + Thread::yield(); + } + } + + HighResolutionTimer& owner; + int volatile periodMs; + +private: + pthread_t thread; + bool volatile shouldStop; + + static void* timerThread (void* param) + { + #if ! BEAST_ANDROID + int dummy; + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); + #endif + + reinterpret_cast (param)->timerThread(); + return nullptr; + } + + void timerThread() + { + Clock clock (periodMs); + + while (! shouldStop) + { + clock.wait(); + owner.hiResTimerCallback(); + } + + periodMs = 0; + thread = 0; + } + + struct Clock + { + #if BEAST_MAC || BEAST_IOS + Clock (double millis) noexcept + { + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + delta = (((uint64_t) (millis * 1000000.0)) * timebase.numer) / timebase.denom; + time = mach_absolute_time(); + } + + void wait() noexcept + { + time += delta; + mach_wait_until (time); + } + + uint64_t time, delta; + + #elif BEAST_ANDROID + Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) + { + } + + void wait() noexcept + { + struct timespec t; + t.tv_sec = (time_t) (delta / 1000000000); + t.tv_nsec = (long) (delta % 1000000000); + nanosleep (&t, nullptr); + } + + uint64 delta; + #else + Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) + { + struct timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + time = 1000000000 * (int64) t.tv_sec + t.tv_nsec; + } + + void wait() noexcept + { + time += delta; + + struct timespec t; + t.tv_sec = (time_t) (time / 1000000000); + t.tv_nsec = (long) (time % 1000000000); + + clock_nanosleep (CLOCK_MONOTONIC, TIMER_ABSTIME, &t, nullptr); + } + + uint64 time, delta; + #endif + }; + + static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) + { + #if BEAST_MAC || BEAST_IOS + thread_time_constraint_policy_data_t policy; + policy.period = (uint32_t) (periodMs * 1000000); + policy.computation = 50000; + policy.constraint = policy.period; + policy.preemptible = true; + + return thread_policy_set (pthread_mach_thread_np (thread), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t) &policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; + + #else + (void) periodMs; + struct sched_param param; + param.sched_priority = sched_get_priority_max (SCHED_RR); + return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; + + #endif + } + + BEAST_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/modules/beast_core/native/beast_win32_ComSmartPtr.h b/modules/beast_core/native/beast_win32_ComSmartPtr.h new file mode 100644 index 0000000000..ee25d3ff38 --- /dev/null +++ b/modules/beast_core/native/beast_win32_ComSmartPtr.h @@ -0,0 +1,165 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WIN32_COMSMARTPTR_BEASTHEADER +#define BEAST_WIN32_COMSMARTPTR_BEASTHEADER + +#ifndef _MSC_VER +template struct UUIDGetter { static CLSID get() { jassertfalse; return CLSID(); } }; +#define __uuidof(x) UUIDGetter::get() +#endif + +inline GUID uuidFromString (const char* const s) noexcept +{ + unsigned long p0; + unsigned int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + + #ifndef _MSC_VER + sscanf + #else + sscanf_s + #endif + (s, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + &p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10); + + GUID g = { p0, (uint16) p1, (uint16) p2, { (uint8) p3, (uint8) p4, (uint8) p5, (uint8) p6, + (uint8) p7, (uint8) p8, (uint8) p9, (uint8) p10 }}; + return g; +} + +//============================================================================== +/** A simple COM smart pointer. +*/ +template +class ComSmartPtr +{ +public: + ComSmartPtr() throw() : p (0) {} + ComSmartPtr (ComClass* const obj) : p (obj) { if (p) p->AddRef(); } + ComSmartPtr (const ComSmartPtr& other) : p (other.p) { if (p) p->AddRef(); } + ~ComSmartPtr() { release(); } + + operator ComClass*() const throw() { return p; } + ComClass& operator*() const throw() { return *p; } + ComClass* operator->() const throw() { return p; } + + ComSmartPtr& operator= (ComClass* const newP) + { + if (newP != 0) newP->AddRef(); + release(); + p = newP; + return *this; + } + + ComSmartPtr& operator= (const ComSmartPtr& newP) { return operator= (newP.p); } + + // Releases and nullifies this pointer and returns its address + ComClass** resetAndGetPointerAddress() + { + release(); + p = 0; + return &p; + } + + HRESULT CoCreateInstance (REFCLSID classUUID, DWORD dwClsContext = CLSCTX_INPROC_SERVER) + { + HRESULT hr = ::CoCreateInstance (classUUID, 0, dwClsContext, __uuidof (ComClass), (void**) resetAndGetPointerAddress()); + bassert (hr != CO_E_NOTINITIALIZED); // You haven't called CoInitialize for the current thread! + return hr; + } + + template + HRESULT QueryInterface (REFCLSID classUUID, ComSmartPtr& destObject) const + { + if (p == 0) + return E_POINTER; + + return p->QueryInterface (classUUID, (void**) destObject.resetAndGetPointerAddress()); + } + + template + HRESULT QueryInterface (ComSmartPtr& destObject) const + { + return this->QueryInterface (__uuidof (OtherComClass), destObject); + } + +private: + ComClass* p; + + void release() { if (p != 0) p->Release(); } + + ComClass** operator&() throw(); // private to avoid it being used accidentally +}; + +//============================================================================== +#define BEAST_COMRESULT HRESULT __stdcall + +//============================================================================== +template +class ComBaseClassHelperBase : public ComClass +{ +public: + ComBaseClassHelperBase (unsigned int initialRefCount) : refCount (initialRefCount) {} + virtual ~ComBaseClassHelperBase() {} + + ULONG __stdcall AddRef() { return ++refCount; } + ULONG __stdcall Release() { const ULONG r = --refCount; if (r == 0) delete this; return r; } + +protected: + ULONG refCount; + + BEAST_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == IID_IUnknown) + return castToType (result); + + *result = 0; + return E_NOINTERFACE; + } + + template + BEAST_COMRESULT castToType (void** result) + { + this->AddRef(); *result = dynamic_cast (this); return S_OK; + } +}; + +/** Handy base class for writing COM objects, providing ref-counting and a basic QueryInterface method. +*/ +template +class ComBaseClassHelper : public ComBaseClassHelperBase +{ +public: + ComBaseClassHelper (unsigned int initialRefCount = 1) : ComBaseClassHelperBase (initialRefCount) {} + ~ComBaseClassHelper() {} + + BEAST_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == __uuidof (ComClass)) + return this->template castToType (result); + + return ComBaseClassHelperBase ::QueryInterface (refId, result); + } +}; + +#endif // BEAST_WIN32_COMSMARTPTR_BEASTHEADER diff --git a/modules/beast_core/native/beast_win32_Files.cpp b/modules/beast_core/native/beast_win32_Files.cpp new file mode 100644 index 0000000000..c58d226018 --- /dev/null +++ b/modules/beast_core/native/beast_win32_Files.cpp @@ -0,0 +1,956 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef INVALID_FILE_ATTRIBUTES + #define INVALID_FILE_ATTRIBUTES ((DWORD) -1) +#endif + +//============================================================================== +namespace WindowsFileHelpers +{ + DWORD getAtts (const String& path) + { + return GetFileAttributes (path.toWideCharPointer()); + } + + int64 fileTimeToTime (const FILETIME* const ft) + { + static_bassert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME)); // tell me if this fails! + + return (int64) ((reinterpret_cast (ft)->QuadPart - literal64bit (116444736000000000)) / 10000); + } + + FILETIME* timeToFileTime (const int64 time, FILETIME* const ft) noexcept + { + if (time <= 0) + return nullptr; + + reinterpret_cast (ft)->QuadPart = (ULONGLONG) (time * 10000 + literal64bit (116444736000000000)); + return ft; + } + + String getDriveFromPath (String path) + { + if (path.isNotEmpty() && path[1] == ':' && path[2] == 0) + path << '\\'; + + const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (path.getCharPointer()) + 4; + HeapBlock pathCopy; + pathCopy.calloc (numBytes, 1); + path.copyToUTF16 (pathCopy, numBytes); + + if (PathStripToRoot (pathCopy)) + path = static_cast (pathCopy); + + return path; + } + + int64 getDiskSpaceInfo (const String& path, const bool total) + { + ULARGE_INTEGER spc, tot, totFree; + + if (GetDiskFreeSpaceEx (getDriveFromPath (path).toWideCharPointer(), &spc, &tot, &totFree)) + return total ? (int64) tot.QuadPart + : (int64) spc.QuadPart; + + return 0; + } + + unsigned int getWindowsDriveType (const String& path) + { + return GetDriveType (getDriveFromPath (path).toWideCharPointer()); + } + + File getSpecialFolderPath (int type) + { + WCHAR path [MAX_PATH + 256]; + + if (SHGetSpecialFolderPath (0, path, type, FALSE)) + return File (String (path)); + + return File::nonexistent; + } + + File getModuleFileName (HINSTANCE moduleHandle) + { + WCHAR dest [MAX_PATH + 256]; + dest[0] = 0; + GetModuleFileName (moduleHandle, dest, (DWORD) numElementsInArray (dest)); + return File (String (dest)); + } + + Result getResultForLastError() + { + TCHAR messageBuffer [256] = { 0 }; + + FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr); + + return Result::fail (String (messageBuffer)); + } +} + +//============================================================================== +const beast_wchar File::separator = '\\'; +const String File::separatorString ("\\"); + + +//============================================================================== +bool File::exists() const +{ + return fullPath.isNotEmpty() + && WindowsFileHelpers::getAtts (fullPath) != INVALID_FILE_ATTRIBUTES; +} + +bool File::existsAsFile() const +{ + return fullPath.isNotEmpty() + && (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +bool File::isDirectory() const +{ + const DWORD attr = WindowsFileHelpers::getAtts (fullPath); + return ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) && (attr != INVALID_FILE_ATTRIBUTES); +} + +bool File::hasWriteAccess() const +{ + if (exists()) + return (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_READONLY) == 0; + + // on windows, it seems that even read-only directories can still be written into, + // so checking the parent directory's permissions would return the wrong result.. + return true; +} + +bool File::setFileReadOnlyInternal (const bool shouldBeReadOnly) const +{ + const DWORD oldAtts = WindowsFileHelpers::getAtts (fullPath); + + if (oldAtts == INVALID_FILE_ATTRIBUTES) + return false; + + const DWORD newAtts = shouldBeReadOnly ? (oldAtts | FILE_ATTRIBUTE_READONLY) + : (oldAtts & ~FILE_ATTRIBUTE_READONLY); + return newAtts == oldAtts + || SetFileAttributes (fullPath.toWideCharPointer(), newAtts) != FALSE; +} + +bool File::isHidden() const +{ + return (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_HIDDEN) != 0; +} + +//============================================================================== +bool File::deleteFile() const +{ + if (! exists()) + return true; + + return isDirectory() ? RemoveDirectory (fullPath.toWideCharPointer()) != 0 + : DeleteFile (fullPath.toWideCharPointer()) != 0; +} + +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + // The string we pass in must be double null terminated.. + const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (fullPath.getCharPointer()) + 8; + HeapBlock doubleNullTermPath; + doubleNullTermPath.calloc (numBytes, 1); + fullPath.copyToUTF16 (doubleNullTermPath, numBytes); + + SHFILEOPSTRUCT fos = { 0 }; + fos.wFunc = FO_DELETE; + fos.pFrom = doubleNullTermPath; + fos.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION + | FOF_NOCONFIRMMKDIR | FOF_RENAMEONCOLLISION; + + return SHFileOperation (&fos) == 0; +} + +bool File::copyInternal (const File& dest) const +{ + return CopyFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer(), false) != 0; +} + +bool File::moveInternal (const File& dest) const +{ + return MoveFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer()) != 0; +} + +Result File::createDirectoryInternal (const String& fileName) const +{ + return CreateDirectory (fileName.toWideCharPointer(), 0) ? Result::ok() + : WindowsFileHelpers::getResultForLastError(); +} + +//============================================================================== +int64 beast_fileSetPosition (void* handle, int64 pos) +{ + LARGE_INTEGER li; + li.QuadPart = pos; + li.LowPart = SetFilePointer ((HANDLE) handle, (LONG) li.LowPart, &li.HighPart, FILE_BEGIN); // (returns -1 if it fails) + return li.QuadPart; +} + +void FileInputStream::openHandle() +{ + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); + + if (h != INVALID_HANDLE_VALUE) + fileHandle = (void*) h; + else + status = WindowsFileHelpers::getResultForLastError(); +} + +void FileInputStream::closeHandle() +{ + CloseHandle ((HANDLE) fileHandle); +} + +size_t FileInputStream::readInternal (void* buffer, size_t numBytes) +{ + if (fileHandle != 0) + { + DWORD actualNum = 0; + if (! ReadFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, 0)) + status = WindowsFileHelpers::getResultForLastError(); + + return (size_t) actualNum; + } + + return 0; +} + +//============================================================================== +void FileOutputStream::openHandle() +{ + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (h != INVALID_HANDLE_VALUE) + { + LARGE_INTEGER li; + li.QuadPart = 0; + li.LowPart = SetFilePointer (h, 0, &li.HighPart, FILE_END); + + if (li.LowPart != INVALID_SET_FILE_POINTER) + { + fileHandle = (void*) h; + currentPosition = li.QuadPart; + return; + } + } + + status = WindowsFileHelpers::getResultForLastError(); +} + +void FileOutputStream::closeHandle() +{ + CloseHandle ((HANDLE) fileHandle); +} + +ssize_t FileOutputStream::writeInternal (const void* buffer, size_t numBytes) +{ + if (fileHandle != nullptr) + { + DWORD actualNum = 0; + if (! WriteFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, 0)) + status = WindowsFileHelpers::getResultForLastError(); + + return (ssize_t) actualNum; + } + + return 0; +} + +void FileOutputStream::flushInternal() +{ + if (fileHandle != nullptr) + if (! FlushFileBuffers ((HANDLE) fileHandle)) + status = WindowsFileHelpers::getResultForLastError(); +} + +Result FileOutputStream::truncate() +{ + if (fileHandle == nullptr) + return status; + + flush(); + return SetEndOfFile ((HANDLE) fileHandle) ? Result::ok() + : WindowsFileHelpers::getResultForLastError(); +} + +//============================================================================== +void MemoryMappedFile::openInternal (const File& file, AccessMode mode) +{ + bassert (mode == readOnly || mode == readWrite); + + if (range.getStart() > 0) + { + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + + range.setStart (range.getStart() - (range.getStart() % systemInfo.dwAllocationGranularity)); + } + + DWORD accessMode = GENERIC_READ, createType = OPEN_EXISTING; + DWORD protect = PAGE_READONLY, access = FILE_MAP_READ; + + if (mode == readWrite) + { + accessMode = GENERIC_READ | GENERIC_WRITE; + createType = OPEN_ALWAYS; + protect = PAGE_READWRITE; + access = FILE_MAP_ALL_ACCESS; + } + + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode, FILE_SHARE_READ, 0, + createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); + + if (h != INVALID_HANDLE_VALUE) + { + fileHandle = (void*) h; + + HANDLE mappingHandle = CreateFileMapping (h, 0, protect, (DWORD) (range.getEnd() >> 32), (DWORD) range.getEnd(), 0); + + if (mappingHandle != 0) + { + address = MapViewOfFile (mappingHandle, access, (DWORD) (range.getStart() >> 32), + (DWORD) range.getStart(), (SIZE_T) range.getLength()); + + if (address == nullptr) + range = Range(); + + CloseHandle (mappingHandle); + } + } +} + +MemoryMappedFile::~MemoryMappedFile() +{ + if (address != nullptr) + UnmapViewOfFile (address); + + if (fileHandle != nullptr) + CloseHandle ((HANDLE) fileHandle); +} + +//============================================================================== +int64 File::getSize() const +{ + WIN32_FILE_ATTRIBUTE_DATA attributes; + + if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes)) + return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow; + + return 0; +} + +void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const +{ + using namespace WindowsFileHelpers; + WIN32_FILE_ATTRIBUTE_DATA attributes; + + if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes)) + { + modificationTime = fileTimeToTime (&attributes.ftLastWriteTime); + creationTime = fileTimeToTime (&attributes.ftCreationTime); + accessTime = fileTimeToTime (&attributes.ftLastAccessTime); + } + else + { + creationTime = accessTime = modificationTime = 0; + } +} + +bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 creationTime) const +{ + using namespace WindowsFileHelpers; + + bool ok = false; + HANDLE h = CreateFile (fullPath.toWideCharPointer(), GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (h != INVALID_HANDLE_VALUE) + { + FILETIME m, a, c; + + ok = SetFileTime (h, + timeToFileTime (creationTime, &c), + timeToFileTime (accessTime, &a), + timeToFileTime (modificationTime, &m)) != 0; + + CloseHandle (h); + } + + return ok; +} + +//============================================================================== +void File::findFileSystemRoots (Array& destArray) +{ + TCHAR buffer [2048] = { 0 }; + GetLogicalDriveStrings (2048, buffer); + + const TCHAR* n = buffer; + StringArray roots; + + while (*n != 0) + { + roots.add (String (n)); + + while (*n++ != 0) + {} + } + + roots.sort (true); + + for (int i = 0; i < roots.size(); ++i) + destArray.add (roots [i]); +} + +//============================================================================== +String File::getVolumeLabel() const +{ + TCHAR dest[64]; + if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest, + (DWORD) numElementsInArray (dest), 0, 0, 0, 0, 0)) + dest[0] = 0; + + return dest; +} + +int File::getVolumeSerialNumber() const +{ + TCHAR dest[64]; + DWORD serialNum; + + if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest, + (DWORD) numElementsInArray (dest), &serialNum, 0, 0, 0, 0)) + return 0; + + return (int) serialNum; +} + +int64 File::getBytesFreeOnVolume() const +{ + return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), false); +} + +int64 File::getVolumeTotalSize() const +{ + return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), true); +} + +//============================================================================== +bool File::isOnCDRomDrive() const +{ + return WindowsFileHelpers::getWindowsDriveType (getFullPathName()) == DRIVE_CDROM; +} + +bool File::isOnHardDisk() const +{ + if (fullPath.isEmpty()) + return false; + + const unsigned int n = WindowsFileHelpers::getWindowsDriveType (getFullPathName()); + + if (fullPath.toLowerCase()[0] <= 'b' && fullPath[1] == ':') + return n != DRIVE_REMOVABLE; + + return n != DRIVE_CDROM && n != DRIVE_REMOTE; +} + +bool File::isOnRemovableDrive() const +{ + if (fullPath.isEmpty()) + return false; + + const unsigned int n = WindowsFileHelpers::getWindowsDriveType (getFullPathName()); + + return n == DRIVE_CDROM + || n == DRIVE_REMOTE + || n == DRIVE_REMOVABLE + || n == DRIVE_RAMDISK; +} + +//============================================================================== +File BEAST_CALLTYPE File::getSpecialLocation (const SpecialLocationType type) +{ + int csidlType = 0; + + switch (type) + { + case userHomeDirectory: csidlType = CSIDL_PROFILE; break; + case userDocumentsDirectory: csidlType = CSIDL_PERSONAL; break; + case userDesktopDirectory: csidlType = CSIDL_DESKTOP; break; + case userApplicationDataDirectory: csidlType = CSIDL_APPDATA; break; + case commonApplicationDataDirectory: csidlType = CSIDL_COMMON_APPDATA; break; + case globalApplicationsDirectory: csidlType = CSIDL_PROGRAM_FILES; break; + case userMusicDirectory: csidlType = 0x0d; /*CSIDL_MYMUSIC*/ break; + case userMoviesDirectory: csidlType = 0x0e; /*CSIDL_MYVIDEO*/ break; + case userPicturesDirectory: csidlType = 0x27; /*CSIDL_MYPICTURES*/ break; + + case tempDirectory: + { + WCHAR dest [2048]; + dest[0] = 0; + GetTempPath ((DWORD) numElementsInArray (dest), dest); + return File (String (dest)); + } + + case invokedExecutableFile: + case currentExecutableFile: + case currentApplicationFile: + return WindowsFileHelpers::getModuleFileName ((HINSTANCE) Process::getCurrentModuleInstanceHandle()); + + case hostApplicationPath: + return WindowsFileHelpers::getModuleFileName (0); + + default: + jassertfalse; // unknown type? + return File::nonexistent; + } + + return WindowsFileHelpers::getSpecialFolderPath (csidlType); +} + +//============================================================================== +File File::getCurrentWorkingDirectory() +{ + WCHAR dest [MAX_PATH + 256]; + dest[0] = 0; + GetCurrentDirectory ((DWORD) numElementsInArray (dest), dest); + return File (String (dest)); +} + +bool File::setAsCurrentWorkingDirectory() const +{ + return SetCurrentDirectory (getFullPathName().toWideCharPointer()) != FALSE; +} + +//============================================================================== +String File::getVersion() const +{ + String result; + + DWORD handle = 0; + DWORD bufferSize = GetFileVersionInfoSize (getFullPathName().toWideCharPointer(), &handle); + HeapBlock buffer; + buffer.calloc (bufferSize); + + if (GetFileVersionInfo (getFullPathName().toWideCharPointer(), 0, bufferSize, buffer)) + { + VS_FIXEDFILEINFO* vffi; + UINT len = 0; + + if (VerQueryValue (buffer, (LPTSTR) _T("\\"), (LPVOID*) &vffi, &len)) + { + result << (int) HIWORD (vffi->dwFileVersionMS) << '.' + << (int) LOWORD (vffi->dwFileVersionMS) << '.' + << (int) HIWORD (vffi->dwFileVersionLS) << '.' + << (int) LOWORD (vffi->dwFileVersionLS); + } + } + + return result; +} + +//============================================================================== +File File::getLinkedTarget() const +{ + File result (*this); + String p (getFullPathName()); + + if (! exists()) + p += ".lnk"; + else if (! hasFileExtension (".lnk")) + return result; + + ComSmartPtr shellLink; + ComSmartPtr persistFile; + + if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink)) + && SUCCEEDED (shellLink.QueryInterface (persistFile)) + && SUCCEEDED (persistFile->Load (p.toWideCharPointer(), STGM_READ)) + && SUCCEEDED (shellLink->Resolve (0, SLR_ANY_MATCH | SLR_NO_UI))) + { + WIN32_FIND_DATA winFindData; + WCHAR resolvedPath [MAX_PATH]; + + if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, SLGP_UNCPRIORITY))) + result = File (resolvedPath); + } + + return result; +} + +bool File::createLink (const String& description, const File& linkFileToCreate) const +{ + linkFileToCreate.deleteFile(); + + ComSmartPtr shellLink; + ComSmartPtr persistFile; + + return SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink)) + && SUCCEEDED (shellLink->SetPath (getFullPathName().toWideCharPointer())) + && SUCCEEDED (shellLink->SetDescription (description.toWideCharPointer())) + && SUCCEEDED (shellLink.QueryInterface (persistFile)) + && SUCCEEDED (persistFile->Save (linkFileToCreate.getFullPathName().toWideCharPointer(), TRUE)); +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wildCard) + : directoryWithWildCard (File::addTrailingSeparator (directory.getFullPathName()) + wildCard), + handle (INVALID_HANDLE_VALUE) + { + } + + ~Pimpl() + { + if (handle != INVALID_HANDLE_VALUE) + FindClose (handle); + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + using namespace WindowsFileHelpers; + WIN32_FIND_DATA findData; + + if (handle == INVALID_HANDLE_VALUE) + { + handle = FindFirstFile (directoryWithWildCard.toWideCharPointer(), &findData); + + if (handle == INVALID_HANDLE_VALUE) + return false; + } + else + { + if (FindNextFile (handle, &findData) == 0) + return false; + } + + filenameFound = findData.cFileName; + + if (isDir != nullptr) *isDir = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + if (isHidden != nullptr) *isHidden = ((findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0); + if (isReadOnly != nullptr) *isReadOnly = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0); + if (fileSize != nullptr) *fileSize = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32); + if (modTime != nullptr) *modTime = Time (fileTimeToTime (&findData.ftLastWriteTime)); + if (creationTime != nullptr) *creationTime = Time (fileTimeToTime (&findData.ftCreationTime)); + + return true; + } + +private: + const String directoryWithWildCard; + HANDLE handle; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() +{ +} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} + + +//============================================================================== +bool Process::openDocument (const String& fileName, const String& parameters) +{ + HINSTANCE hInstance = 0; + + BEAST_TRY + { + hInstance = ShellExecute (0, 0, fileName.toWideCharPointer(), + parameters.toWideCharPointer(), 0, SW_SHOWDEFAULT); + } + BEAST_CATCH_ALL + + return hInstance > (HINSTANCE) 32; +} + +void File::revealToUser() const +{ + DynamicLibrary dll ("Shell32.dll"); + BEAST_LOAD_WINAPI_FUNCTION (dll, ILCreateFromPathW, ilCreateFromPathW, ITEMIDLIST*, (LPCWSTR)) + BEAST_LOAD_WINAPI_FUNCTION (dll, ILFree, ilFree, void, (ITEMIDLIST*)) + BEAST_LOAD_WINAPI_FUNCTION (dll, SHOpenFolderAndSelectItems, shOpenFolderAndSelectItems, HRESULT, (ITEMIDLIST*, UINT, void*, DWORD)) + + if (ilCreateFromPathW != nullptr && shOpenFolderAndSelectItems != nullptr && ilFree != nullptr) + { + if (ITEMIDLIST* const itemIDList = ilCreateFromPathW (fullPath.toWideCharPointer())) + { + shOpenFolderAndSelectItems (itemIDList, 0, nullptr, 0); + ilFree (itemIDList); + } + } +} + +//============================================================================== +class NamedPipe::Pimpl +{ +public: + Pimpl (const String& pipeName, const bool createPipe) + : filename ("\\\\.\\pipe\\" + File::createLegalFileName (pipeName)), + pipeH (INVALID_HANDLE_VALUE), + cancelEvent (CreateEvent (0, FALSE, FALSE, 0)), + connected (false), ownsPipe (createPipe), shouldStop (false) + { + if (createPipe) + pipeH = CreateNamedPipe (filename.toWideCharPointer(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, + PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, 0); + } + + ~Pimpl() + { + disconnectPipe(); + + if (pipeH != INVALID_HANDLE_VALUE) + CloseHandle (pipeH); + + CloseHandle (cancelEvent); + } + + bool connect (const int timeOutMs) + { + if (! ownsPipe) + { + if (pipeH != INVALID_HANDLE_VALUE) + return true; + + const Time timeOutEnd (Time::getCurrentTime() + RelativeTime::milliseconds (timeOutMs)); + + for (;;) + { + { + const ScopedLock sl (createFileLock); + + if (pipeH == INVALID_HANDLE_VALUE) + pipeH = CreateFile (filename.toWideCharPointer(), + GENERIC_READ | GENERIC_WRITE, 0, 0, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + } + + if (pipeH != INVALID_HANDLE_VALUE) + return true; + + if (shouldStop || (timeOutMs >= 0 && Time::getCurrentTime() > timeOutEnd)) + return false; + + Thread::sleep (1); + } + } + + if (! connected) + { + OverlappedEvent over; + + if (ConnectNamedPipe (pipeH, &over.over) == 0) + { + switch (GetLastError()) + { + case ERROR_PIPE_CONNECTED: connected = true; break; + case ERROR_IO_PENDING: + case ERROR_PIPE_LISTENING: connected = waitForIO (over, timeOutMs); break; + default: break; + } + } + } + + return connected; + } + + void disconnectPipe() + { + if (ownsPipe && connected) + { + DisconnectNamedPipe (pipeH); + connected = false; + } + } + + int read (void* destBuffer, const int maxBytesToRead, const int timeOutMilliseconds) + { + while (connect (timeOutMilliseconds)) + { + if (maxBytesToRead <= 0) + return 0; + + OverlappedEvent over; + unsigned long numRead; + + if (ReadFile (pipeH, destBuffer, (DWORD) maxBytesToRead, &numRead, &over.over)) + return (int) numRead; + + const DWORD lastError = GetLastError(); + + if (lastError == ERROR_IO_PENDING) + { + if (! waitForIO (over, timeOutMilliseconds)) + return -1; + + if (GetOverlappedResult (pipeH, &over.over, &numRead, FALSE)) + return (int) numRead; + } + + if (ownsPipe && (GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_PIPE_NOT_CONNECTED)) + disconnectPipe(); + else + break; + } + + return -1; + } + + int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) + { + if (connect (timeOutMilliseconds)) + { + if (numBytesToWrite <= 0) + return 0; + + OverlappedEvent over; + unsigned long numWritten; + + if (WriteFile (pipeH, sourceBuffer, (DWORD) numBytesToWrite, &numWritten, &over.over)) + return (int) numWritten; + + if (GetLastError() == ERROR_IO_PENDING) + { + if (! waitForIO (over, timeOutMilliseconds)) + return -1; + + if (GetOverlappedResult (pipeH, &over.over, &numWritten, FALSE)) + return (int) numWritten; + + if (GetLastError() == ERROR_BROKEN_PIPE && ownsPipe) + disconnectPipe(); + } + } + + return -1; + } + + const String filename; + HANDLE pipeH, cancelEvent; + bool connected, ownsPipe, shouldStop; + CriticalSection createFileLock; + +private: + struct OverlappedEvent + { + OverlappedEvent() + { + zerostruct (over); + over.hEvent = CreateEvent (0, TRUE, FALSE, 0); + } + + ~OverlappedEvent() + { + CloseHandle (over.hEvent); + } + + OVERLAPPED over; + }; + + bool waitForIO (OverlappedEvent& over, int timeOutMilliseconds) + { + if (shouldStop) + return false; + + HANDLE handles[] = { over.over.hEvent, cancelEvent }; + DWORD waitResult = WaitForMultipleObjects (2, handles, FALSE, + timeOutMilliseconds >= 0 ? timeOutMilliseconds + : INFINITE); + + if (waitResult == WAIT_OBJECT_0) + return true; + + CancelIo (pipeH); + return false; + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +void NamedPipe::close() +{ + if (pimpl != nullptr) + { + pimpl->shouldStop = true; + SetEvent (pimpl->cancelEvent); + + ScopedWriteLock sl (lock); + pimpl = nullptr; + } +} + +bool NamedPipe::openInternal (const String& pipeName, const bool createPipe) +{ + pimpl = new Pimpl (pipeName, createPipe); + + if (createPipe && pimpl->pipeH == INVALID_HANDLE_VALUE) + { + pimpl = nullptr; + return false; + } + + return true; +} + +int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->read (destBuffer, maxBytesToRead, timeOutMilliseconds) : -1; +} + +int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->write (sourceBuffer, numBytesToWrite, timeOutMilliseconds) : -1; +} diff --git a/modules/beast_core/native/beast_win32_Network.cpp b/modules/beast_core/native/beast_win32_Network.cpp new file mode 100644 index 0000000000..7edad70888 --- /dev/null +++ b/modules/beast_core/native/beast_win32_Network.cpp @@ -0,0 +1,464 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef INTERNET_FLAG_NEED_FILE + #define INTERNET_FLAG_NEED_FILE 0x00000010 +#endif + +#ifndef INTERNET_OPTION_DISABLE_AUTODIAL + #define INTERNET_OPTION_DISABLE_AUTODIAL 70 +#endif + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) + : connection (0), request (0), + address (address_), headers (headers_), postData (postData_), position (0), + finished (false), isPost (isPost_), timeOutMs (timeOutMs_) + { + createConnection (progressCallback, progressCallbackContext); + + if (responseHeaders != nullptr && ! isError()) + { + DWORD bufferSizeBytes = 4096; + + for (;;) + { + HeapBlock buffer ((size_t) bufferSizeBytes); + + if (HttpQueryInfo (request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer.getData(), &bufferSizeBytes, 0)) + { + StringArray headersArray; + headersArray.addLines (reinterpret_cast (buffer.getData())); + + for (int i = 0; i < headersArray.size(); ++i) + { + const String& header = headersArray[i]; + const String key (header.upToFirstOccurrenceOf (": ", false, false)); + const String value (header.fromFirstOccurrenceOf (": ", false, false)); + const String previousValue ((*responseHeaders) [key]); + + responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + } + + break; + } + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; + } + } + } + + ~WebInputStream() + { + close(); + } + + //============================================================================== + bool isError() const { return request == 0; } + bool isExhausted() { return finished; } + int64 getPosition() { return position; } + + int64 getTotalLength() + { + if (! isError()) + { + DWORD index = 0, result = 0, size = sizeof (result); + + if (HttpQueryInfo (request, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &result, &size, &index)) + return (int64) result; + } + + return -1; + } + + int read (void* buffer, int bytesToRead) + { + bassert (buffer != nullptr && bytesToRead >= 0); + DWORD bytesRead = 0; + + if (! (finished || isError())) + { + InternetReadFile (request, buffer, (DWORD) bytesToRead, &bytesRead); + position += bytesRead; + + if (bytesRead == 0) + finished = true; + } + + return (int) bytesRead; + } + + bool setPosition (int64 wantedPos) + { + if (isError()) + return false; + + if (wantedPos != position) + { + finished = false; + position = (int64) InternetSetFilePointer (request, (LONG) wantedPos, 0, FILE_BEGIN, 0); + + if (position == wantedPos) + return true; + + if (wantedPos < position) + { + close(); + position = 0; + createConnection (0, 0); + } + + skipNextBytes (wantedPos - position); + } + + return true; + } + +private: + //============================================================================== + HINTERNET connection, request; + String address, headers; + MemoryBlock postData; + int64 position; + bool finished; + const bool isPost; + int timeOutMs; + + void close() + { + if (request != 0) + { + InternetCloseHandle (request); + request = 0; + } + + if (connection != 0) + { + InternetCloseHandle (connection); + connection = 0; + } + } + + void createConnection (URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + static HINTERNET sessionHandle = InternetOpen (_T("beast"), INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0); + + close(); + + if (sessionHandle != 0) + { + // break up the url.. + const int fileNumChars = 65536; + const int serverNumChars = 2048; + const int usernameNumChars = 1024; + const int passwordNumChars = 1024; + HeapBlock file (fileNumChars), server (serverNumChars), + username (usernameNumChars), password (passwordNumChars); + + URL_COMPONENTS uc = { 0 }; + uc.dwStructSize = sizeof (uc); + uc.lpszUrlPath = file; + uc.dwUrlPathLength = fileNumChars; + uc.lpszHostName = server; + uc.dwHostNameLength = serverNumChars; + uc.lpszUserName = username; + uc.dwUserNameLength = usernameNumChars; + uc.lpszPassword = password; + uc.dwPasswordLength = passwordNumChars; + + if (InternetCrackUrl (address.toWideCharPointer(), 0, 0, &uc)) + openConnection (uc, sessionHandle, progressCallback, progressCallbackContext); + } + } + + void openConnection (URL_COMPONENTS& uc, HINTERNET sessionHandle, + URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + int disable = 1; + InternetSetOption (sessionHandle, INTERNET_OPTION_DISABLE_AUTODIAL, &disable, sizeof (disable)); + + if (timeOutMs == 0) + timeOutMs = 30000; + else if (timeOutMs < 0) + timeOutMs = -1; + + applyTimeout (sessionHandle, INTERNET_OPTION_CONNECT_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_RECEIVE_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_SEND_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_DATA_SEND_TIMEOUT); + + const bool isFtp = address.startsWithIgnoreCase ("ftp:"); + + connection = InternetConnect (sessionHandle, uc.lpszHostName, uc.nPort, + uc.lpszUserName, uc.lpszPassword, + isFtp ? (DWORD) INTERNET_SERVICE_FTP + : (DWORD) INTERNET_SERVICE_HTTP, + 0, 0); + if (connection != 0) + { + if (isFtp) + request = FtpOpenFile (connection, uc.lpszUrlPath, GENERIC_READ, + FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_NEED_FILE, 0); + else + openHTTPConnection (uc, progressCallback, progressCallbackContext); + } + } + + void applyTimeout (HINTERNET sessionHandle, const DWORD option) + { + InternetSetOption (sessionHandle, option, &timeOutMs, sizeof (timeOutMs)); + } + + void openHTTPConnection (URL_COMPONENTS& uc, URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + const TCHAR* mimeTypes[] = { _T("*/*"), nullptr }; + + DWORD flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES; + + if (address.startsWithIgnoreCase ("https:")) + flags |= INTERNET_FLAG_SECURE; // (this flag only seems necessary if the OS is running IE6 - + // IE7 seems to automatically work out when it's https) + + request = HttpOpenRequest (connection, isPost ? _T("POST") : _T("GET"), + uc.lpszUrlPath, 0, 0, mimeTypes, flags, 0); + + if (request != 0) + { + INTERNET_BUFFERS buffers = { 0 }; + buffers.dwStructSize = sizeof (INTERNET_BUFFERS); + buffers.lpcszHeader = headers.toWideCharPointer(); + buffers.dwHeadersLength = (DWORD) headers.length(); + buffers.dwBufferTotal = (DWORD) postData.getSize(); + + if (HttpSendRequestEx (request, &buffers, 0, HSR_INITIATE, 0)) + { + int bytesSent = 0; + + for (;;) + { + const int bytesToDo = bmin (1024, (int) postData.getSize() - bytesSent); + DWORD bytesDone = 0; + + if (bytesToDo > 0 + && ! InternetWriteFile (request, + static_cast (postData.getData()) + bytesSent, + (DWORD) bytesToDo, &bytesDone)) + { + break; + } + + if (bytesToDo == 0 || (int) bytesDone < bytesToDo) + { + if (HttpEndRequest (request, 0, 0, 0)) + return; + + break; + } + + bytesSent += bytesDone; + + if (progressCallback != nullptr + && ! progressCallback (progressCallbackContext, bytesSent, (int) postData.getSize())) + break; + } + } + } + + close(); + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; + +InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, + OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers, const int timeOutMs, StringPairArray* responseHeaders) +{ + ScopedPointer wi (new WebInputStream (address, isPost, postData, + progressCallback, progressCallbackContext, + headers, timeOutMs, responseHeaders)); + + return wi->isError() ? nullptr : wi.release(); +} + + +//============================================================================== +struct GetAdaptersInfoHelper +{ + bool callGetAdaptersInfo() + { + DynamicLibrary dll ("iphlpapi.dll"); + BEAST_LOAD_WINAPI_FUNCTION (dll, GetAdaptersInfo, getAdaptersInfo, DWORD, (PIP_ADAPTER_INFO, PULONG)) + + if (getAdaptersInfo == nullptr) + return false; + + adapterInfo.malloc (1); + ULONG len = sizeof (IP_ADAPTER_INFO); + + if (getAdaptersInfo (adapterInfo, &len) == ERROR_BUFFER_OVERFLOW) + adapterInfo.malloc (len, 1); + + return getAdaptersInfo (adapterInfo, &len) == NO_ERROR; + } + + HeapBlock adapterInfo; +}; + +namespace MACAddressHelpers +{ + void getViaGetAdaptersInfo (Array& result) + { + GetAdaptersInfoHelper gah; + + if (gah.callGetAdaptersInfo()) + { + for (PIP_ADAPTER_INFO adapter = gah.adapterInfo; adapter != nullptr; adapter = adapter->Next) + if (adapter->AddressLength >= 6) + result.addIfNotAlreadyThere (MACAddress (adapter->Address)); + } + } + + void getViaNetBios (Array& result) + { + DynamicLibrary dll ("netapi32.dll"); + BEAST_LOAD_WINAPI_FUNCTION (dll, Netbios, NetbiosCall, UCHAR, (PNCB)) + + if (NetbiosCall != 0) + { + LANA_ENUM enums = { 0 }; + + { + NCB ncb = { 0 }; + ncb.ncb_command = NCBENUM; + ncb.ncb_buffer = (unsigned char*) &enums; + ncb.ncb_length = sizeof (LANA_ENUM); + NetbiosCall (&ncb); + } + + for (int i = 0; i < enums.length; ++i) + { + NCB ncb2 = { 0 }; + ncb2.ncb_command = NCBRESET; + ncb2.ncb_lana_num = enums.lana[i]; + + if (NetbiosCall (&ncb2) == 0) + { + NCB ncb = { 0 }; + memcpy (ncb.ncb_callname, "* ", NCBNAMSZ); + ncb.ncb_command = NCBASTAT; + ncb.ncb_lana_num = enums.lana[i]; + + struct ASTAT + { + ADAPTER_STATUS adapt; + NAME_BUFFER NameBuff [30]; + }; + + ASTAT astat; + zerostruct (astat); + ncb.ncb_buffer = (unsigned char*) &astat; + ncb.ncb_length = sizeof (ASTAT); + + if (NetbiosCall (&ncb) == 0 && astat.adapt.adapter_type == 0xfe) + result.addIfNotAlreadyThere (MACAddress (astat.adapt.adapter_address)); + } + } + } + } +} + +void MACAddress::findAllAddresses (Array& result) +{ + MACAddressHelpers::getViaGetAdaptersInfo (result); + MACAddressHelpers::getViaNetBios (result); +} + +void IPAddress::findAllAddresses (Array& result) +{ + result.addIfNotAlreadyThere (IPAddress::local()); + + GetAdaptersInfoHelper gah; + + if (gah.callGetAdaptersInfo()) + { + for (PIP_ADAPTER_INFO adapter = gah.adapterInfo; adapter != nullptr; adapter = adapter->Next) + { + IPAddress ip (adapter->IpAddressList.IpAddress.String); + + if (ip != IPAddress::any()) + result.addIfNotAlreadyThere (ip); + } + } +} + +//============================================================================== +bool Process::openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ + DynamicLibrary dll ("MAPI32.dll"); + BEAST_LOAD_WINAPI_FUNCTION (dll, MAPISendMail, mapiSendMail, + ULONG, (LHANDLE, ULONG, lpMapiMessage, ::FLAGS, ULONG)) + + if (mapiSendMail == nullptr) + return false; + + MapiMessage message = { 0 }; + message.lpszSubject = (LPSTR) emailSubject.toRawUTF8(); + message.lpszNoteText = (LPSTR) bodyText.toRawUTF8(); + + MapiRecipDesc recip = { 0 }; + recip.ulRecipClass = MAPI_TO; + String targetEmailAddress_ (targetEmailAddress); + if (targetEmailAddress_.isEmpty()) + targetEmailAddress_ = " "; // (Windows Mail can't deal with a blank address) + recip.lpszName = (LPSTR) targetEmailAddress_.toRawUTF8(); + message.nRecipCount = 1; + message.lpRecips = &recip; + + HeapBlock files; + files.calloc ((size_t) filesToAttach.size()); + + message.nFileCount = (ULONG) filesToAttach.size(); + message.lpFiles = files; + + for (int i = 0; i < filesToAttach.size(); ++i) + { + files[i].nPosition = (ULONG) -1; + files[i].lpszPathName = (LPSTR) filesToAttach[i].toRawUTF8(); + } + + return mapiSendMail (0, 0, &message, MAPI_DIALOG | MAPI_LOGON_UI, 0) == SUCCESS_SUCCESS; +} diff --git a/modules/beast_core/native/beast_win32_Registry.cpp b/modules/beast_core/native/beast_win32_Registry.cpp new file mode 100644 index 0000000000..adc7599c9a --- /dev/null +++ b/modules/beast_core/native/beast_win32_Registry.cpp @@ -0,0 +1,216 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +struct RegistryKeyWrapper +{ + RegistryKeyWrapper (String name, const bool createForWriting, const DWORD wow64Flags) + : key (0), wideCharValueName (nullptr) + { + HKEY rootKey = 0; + + if (name.startsWithIgnoreCase ("HKEY_CURRENT_USER\\")) rootKey = HKEY_CURRENT_USER; + else if (name.startsWithIgnoreCase ("HKEY_LOCAL_MACHINE\\")) rootKey = HKEY_LOCAL_MACHINE; + else if (name.startsWithIgnoreCase ("HKEY_CLASSES_ROOT\\")) rootKey = HKEY_CLASSES_ROOT; + + if (rootKey != 0) + { + name = name.substring (name.indexOfChar ('\\') + 1); + + const int lastSlash = name.lastIndexOfChar ('\\'); + valueName = name.substring (lastSlash + 1); + wideCharValueName = valueName.toWideCharPointer(); + + name = name.substring (0, lastSlash); + const wchar_t* const wideCharName = name.toWideCharPointer(); + DWORD result; + + if (createForWriting) + RegCreateKeyEx (rootKey, wideCharName, 0, 0, REG_OPTION_NON_VOLATILE, + KEY_WRITE | KEY_QUERY_VALUE | wow64Flags, 0, &key, &result); + else + RegOpenKeyEx (rootKey, wideCharName, 0, KEY_READ | wow64Flags, &key); + } + } + + ~RegistryKeyWrapper() + { + if (key != 0) + RegCloseKey (key); + } + + static bool setValue (const String& regValuePath, const DWORD type, + const void* data, size_t dataSize) + { + const RegistryKeyWrapper key (regValuePath, true, 0); + + return key.key != 0 + && RegSetValueEx (key.key, key.wideCharValueName, 0, type, + reinterpret_cast (data), + (DWORD) dataSize) == ERROR_SUCCESS; + } + + static uint32 getBinaryValue (const String& regValuePath, MemoryBlock& result, DWORD wow64Flags) + { + const RegistryKeyWrapper key (regValuePath, false, wow64Flags); + + if (key.key != 0) + { + for (unsigned long bufferSize = 1024; ; bufferSize *= 2) + { + result.setSize (bufferSize, false); + DWORD type = REG_NONE; + + const LONG err = RegQueryValueEx (key.key, key.wideCharValueName, 0, &type, + (LPBYTE) result.getData(), &bufferSize); + + if (err == ERROR_SUCCESS) + { + result.setSize (bufferSize, false); + return type; + } + + if (err != ERROR_MORE_DATA) + break; + } + } + + return REG_NONE; + } + + static String getValue (const String& regValuePath, const String& defaultValue, DWORD wow64Flags) + { + MemoryBlock buffer; + switch (getBinaryValue (regValuePath, buffer, wow64Flags)) + { + case REG_SZ: return static_cast (buffer.getData()); + case REG_DWORD: return String ((int) *reinterpret_cast (buffer.getData())); + default: break; + } + + return defaultValue; + } + + static bool valueExists (const String& regValuePath, const DWORD wow64Flags) + { + const RegistryKeyWrapper key (regValuePath, false, wow64Flags); + + if (key.key == 0) + return false; + + unsigned char buffer [512]; + unsigned long bufferSize = sizeof (buffer); + DWORD type = 0; + + const LONG result = RegQueryValueEx (key.key, key.wideCharValueName, + 0, &type, buffer, &bufferSize); + + return result == ERROR_SUCCESS || result == ERROR_MORE_DATA; + } + + HKEY key; + const wchar_t* wideCharValueName; + String valueName; + + BEAST_DECLARE_NON_COPYABLE (RegistryKeyWrapper) +}; + +uint32 WindowsRegistry::getBinaryValue (const String& regValuePath, MemoryBlock& result) +{ + return RegistryKeyWrapper::getBinaryValue (regValuePath, result, 0); +} + +String WindowsRegistry::getValue (const String& regValuePath, const String& defaultValue) +{ + return RegistryKeyWrapper::getValue (regValuePath, defaultValue, 0); +} + +String WindowsRegistry::getValueWow64 (const String& regValuePath, const String& defaultValue) +{ + return RegistryKeyWrapper::getValue (regValuePath, defaultValue, 0x100 /*KEY_WOW64_64KEY*/); +} + +bool WindowsRegistry::valueExistsWow64 (const String& regValuePath) +{ + return RegistryKeyWrapper::valueExists (regValuePath, 0x100 /*KEY_WOW64_64KEY*/); +} + +bool WindowsRegistry::setValue (const String& regValuePath, const String& value) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_SZ, value.toWideCharPointer(), + CharPointer_UTF16::getBytesRequiredFor (value.getCharPointer())); +} + +bool WindowsRegistry::setValue (const String& regValuePath, const uint32 value) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_DWORD, &value, sizeof (value)); +} + +bool WindowsRegistry::setValue (const String& regValuePath, const uint64 value) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_QWORD, &value, sizeof (value)); +} + +bool WindowsRegistry::setValue (const String& regValuePath, const MemoryBlock& value) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_BINARY, value.getData(), value.getSize()); +} + +bool WindowsRegistry::valueExists (const String& regValuePath) +{ + return RegistryKeyWrapper::valueExists (regValuePath, 0); +} + +void WindowsRegistry::deleteValue (const String& regValuePath) +{ + const RegistryKeyWrapper key (regValuePath, true, 0); + + if (key.key != 0) + RegDeleteValue (key.key, key.wideCharValueName); +} + +void WindowsRegistry::deleteKey (const String& regKeyPath) +{ + const RegistryKeyWrapper key (regKeyPath, true, 0); + + if (key.key != 0) + RegDeleteKey (key.key, key.wideCharValueName); +} + +bool WindowsRegistry::registerFileAssociation (const String& fileExtension, + const String& symbolicDescription, + const String& fullDescription, + const File& targetExecutable, + const int iconResourceNumber, + const bool registerForCurrentUserOnly) +{ + const char* const root = registerForCurrentUserOnly ? "HKEY_CURRENT_USER\\Software\\Classes\\" + : "HKEY_CLASSES_ROOT\\"; + const String key (root + symbolicDescription); + + return setValue (root + fileExtension + "\\", symbolicDescription) + && setValue (key + "\\", fullDescription) + && setValue (key + "\\shell\\open\\command\\", targetExecutable.getFullPathName() + " \"%1\"") + && (iconResourceNumber == 0 + || setValue (key + "\\DefaultIcon\\", + targetExecutable.getFullPathName() + "," + String (-iconResourceNumber))); +} diff --git a/modules/beast_core/native/beast_win32_SystemStats.cpp b/modules/beast_core/native/beast_win32_SystemStats.cpp new file mode 100644 index 0000000000..15696bbff6 --- /dev/null +++ b/modules/beast_core/native/beast_win32_SystemStats.cpp @@ -0,0 +1,413 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +void Logger::outputDebugString (const String& text) +{ + OutputDebugString ((text + "\n").toWideCharPointer()); +} + +//============================================================================== +#ifdef BEAST_DLL_BUILD + BEAST_API void* beastDLL_malloc (size_t sz) { return std::malloc (sz); } + BEAST_API void beastDLL_free (void* block) { std::free (block); } +#endif + +//============================================================================== +#if BEAST_USE_INTRINSICS + +// CPU info functions using intrinsics... + +#pragma intrinsic (__cpuid) +#pragma intrinsic (__rdtsc) + +String SystemStats::getCpuVendor() +{ + int info [4]; + __cpuid (info, 0); + + char v [12]; + memcpy (v, info + 1, 4); + memcpy (v + 4, info + 3, 4); + memcpy (v + 8, info + 2, 4); + + return String (v, 12); +} + +#else + +//============================================================================== +// CPU info functions using old fashioned inline asm... + +static void beast_getCpuVendor (char* const v) +{ + int vendor[4] = { 0 }; + + #if ! BEAST_MINGW + __try + #endif + { + #if BEAST_GCC + unsigned int dummy = 0; + __asm__ ("cpuid" : "=a" (dummy), "=b" (vendor[0]), "=c" (vendor[2]),"=d" (vendor[1]) : "a" (0)); + #else + __asm + { + mov eax, 0 + cpuid + mov [vendor], ebx + mov [vendor + 4], edx + mov [vendor + 8], ecx + } + #endif + } + #if ! BEAST_MINGW + __except (EXCEPTION_EXECUTE_HANDLER) + { + } + #endif + + memcpy (v, vendor, 16); +} + +String SystemStats::getCpuVendor() +{ + char v [16]; + beast_getCpuVendor (v); + return String (v, 16); +} +#endif + + +//============================================================================== +SystemStats::CPUFlags::CPUFlags() +{ + hasMMX = IsProcessorFeaturePresent (PF_MMX_INSTRUCTIONS_AVAILABLE) != 0; + hasSSE = IsProcessorFeaturePresent (PF_XMMI_INSTRUCTIONS_AVAILABLE) != 0; + hasSSE2 = IsProcessorFeaturePresent (PF_XMMI64_INSTRUCTIONS_AVAILABLE) != 0; + #ifdef PF_AMD3D_INSTRUCTIONS_AVAILABLE + has3DNow = IsProcessorFeaturePresent (PF_AMD3D_INSTRUCTIONS_AVAILABLE) != 0; + #else + has3DNow = IsProcessorFeaturePresent (PF_3DNOW_INSTRUCTIONS_AVAILABLE) != 0; + #endif + + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + numCpus = (int) systemInfo.dwNumberOfProcessors; +} + +#if BEAST_MSVC && BEAST_CHECK_MEMORY_LEAKS +struct DebugFlagsInitialiser +{ + DebugFlagsInitialiser() + { + _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + } +}; + +static DebugFlagsInitialiser debugFlagsInitialiser; +#endif + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof (info); + GetVersionEx (&info); + + if (info.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + if (info.dwMajorVersion == 5) + return (info.dwMinorVersion == 0) ? Win2000 : WinXP; + + if (info.dwMajorVersion == 6) + { + switch (info.dwMinorVersion) + { + case 0: return WinVista; + case 1: return Windows7; + case 2: return Windows8; + + default: + jassertfalse; // new version needs to be added here! + return Windows8; + } + } + } + + jassertfalse; // need to support whatever new version is running! + return UnknownOS; +} + +String SystemStats::getOperatingSystemName() +{ + const char* name = "Unknown OS"; + + switch (getOperatingSystemType()) + { + case Windows7: name = "Windows 7"; break; + case Windows8: name = "Windows 8"; break; + case WinVista: name = "Windows Vista"; break; + case WinXP: name = "Windows XP"; break; + case Win2000: name = "Windows 2000"; break; + default: jassertfalse; break; // !! new type of OS? + } + + return name; +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if BEAST_64BIT + return true; + #else + typedef BOOL (WINAPI* LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); + + LPFN_ISWOW64PROCESS fnIsWow64Process + = (LPFN_ISWOW64PROCESS) GetProcAddress (GetModuleHandleA ("kernel32"), "IsWow64Process"); + + BOOL isWow64 = FALSE; + + return fnIsWow64Process != nullptr + && fnIsWow64Process (GetCurrentProcess(), &isWow64) + && isWow64 != FALSE; + #endif +} + +//============================================================================== +int SystemStats::getMemorySizeInMegabytes() +{ + MEMORYSTATUSEX mem; + mem.dwLength = sizeof (mem); + GlobalMemoryStatusEx (&mem); + return (int) (mem.ullTotalPhys / (1024 * 1024)) + 1; +} + +//============================================================================== +String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue) +{ + DWORD len = GetEnvironmentVariableW (name.toWideCharPointer(), nullptr, 0); + if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) + return String (defaultValue); + + HeapBlock buffer (len); + len = GetEnvironmentVariableW (name.toWideCharPointer(), buffer, len); + + return String (CharPointer_wchar_t (buffer), + CharPointer_wchar_t (buffer + len)); +} + +//============================================================================== +uint32 beast_millisecondsSinceStartup() noexcept +{ + return (uint32) timeGetTime(); +} + +//============================================================================== +class HiResCounterHandler +{ +public: + HiResCounterHandler() + : hiResTicksOffset (0) + { + const MMRESULT res = timeBeginPeriod (1); + (void) res; + bassert (res == TIMERR_NOERROR); + + LARGE_INTEGER f; + QueryPerformanceFrequency (&f); + hiResTicksPerSecond = f.QuadPart; + hiResTicksScaleFactor = 1000.0 / hiResTicksPerSecond; + } + + inline int64 getHighResolutionTicks() noexcept + { + LARGE_INTEGER ticks; + QueryPerformanceCounter (&ticks); + + const int64 mainCounterAsHiResTicks = (beast_millisecondsSinceStartup() * hiResTicksPerSecond) / 1000; + const int64 newOffset = mainCounterAsHiResTicks - ticks.QuadPart; + + // fix for a very obscure PCI hardware bug that can make the counter + // sometimes jump forwards by a few seconds.. + const int64 offsetDrift = abs64 (newOffset - hiResTicksOffset); + + if (offsetDrift > (hiResTicksPerSecond >> 1)) + hiResTicksOffset = newOffset; + + return ticks.QuadPart + hiResTicksOffset; + } + + inline double getMillisecondCounterHiRes() noexcept + { + return getHighResolutionTicks() * hiResTicksScaleFactor; + } + + int64 hiResTicksPerSecond, hiResTicksOffset; + double hiResTicksScaleFactor; +}; + +static HiResCounterHandler hiResCounterHandler; + +int64 Time::getHighResolutionTicksPerSecond() noexcept { return hiResCounterHandler.hiResTicksPerSecond; } +int64 Time::getHighResolutionTicks() noexcept { return hiResCounterHandler.getHighResolutionTicks(); } +double Time::getMillisecondCounterHiRes() noexcept { return hiResCounterHandler.getMillisecondCounterHiRes(); } + +//============================================================================== +static int64 beast_getClockCycleCounter() noexcept +{ + #if BEAST_USE_INTRINSICS + // MS intrinsics version... + return (int64) __rdtsc(); + + #elif BEAST_GCC + // GNU inline asm version... + unsigned int hi = 0, lo = 0; + + __asm__ __volatile__ ( + "xor %%eax, %%eax \n\ + xor %%edx, %%edx \n\ + rdtsc \n\ + movl %%eax, %[lo] \n\ + movl %%edx, %[hi]" + : + : [hi] "m" (hi), + [lo] "m" (lo) + : "cc", "eax", "ebx", "ecx", "edx", "memory"); + + return (int64) ((((uint64) hi) << 32) | lo); + #else + // MSVC inline asm version... + unsigned int hi = 0, lo = 0; + + __asm + { + xor eax, eax + xor edx, edx + rdtsc + mov lo, eax + mov hi, edx + } + + return (int64) ((((uint64) hi) << 32) | lo); + #endif +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + const int64 cycles = beast_getClockCycleCounter(); + const uint32 millis = Time::getMillisecondCounter(); + int lastResult = 0; + + for (;;) + { + int n = 1000000; + while (--n > 0) {} + + const uint32 millisElapsed = Time::getMillisecondCounter() - millis; + const int64 cyclesNow = beast_getClockCycleCounter(); + + if (millisElapsed > 80) + { + const int newResult = (int) (((cyclesNow - cycles) / millisElapsed) / 1000); + + if (millisElapsed > 500 || (lastResult == newResult && newResult > 100)) + return newResult; + + lastResult = newResult; + } + } +} + + +//============================================================================== +bool Time::setSystemTimeToThisTime() const +{ + SYSTEMTIME st; + + st.wDayOfWeek = 0; + st.wYear = (WORD) getYear(); + st.wMonth = (WORD) (getMonth() + 1); + st.wDay = (WORD) getDayOfMonth(); + st.wHour = (WORD) getHours(); + st.wMinute = (WORD) getMinutes(); + st.wSecond = (WORD) getSeconds(); + st.wMilliseconds = (WORD) (millisSinceEpoch % 1000); + + // do this twice because of daylight saving conversion problems - the + // first one sets it up, the second one kicks it in. + return SetLocalTime (&st) != 0 + && SetLocalTime (&st) != 0; +} + +int SystemStats::getPageSize() +{ + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + + return (int) systemInfo.dwPageSize; +} + +//============================================================================== +String SystemStats::getLogonName() +{ + TCHAR text [256] = { 0 }; + DWORD len = (DWORD) numElementsInArray (text) - 1; + GetUserName (text, &len); + return String (text, len); +} + +String SystemStats::getFullUserName() +{ + return getLogonName(); +} + +String SystemStats::getComputerName() +{ + TCHAR text [MAX_COMPUTERNAME_LENGTH + 1] = { 0 }; + DWORD len = (DWORD) numElementsInArray (text) - 1; + GetComputerName (text, &len); + return String (text, len); +} + +static String getLocaleValue (LCID locale, LCTYPE key, const char* defaultValue) +{ + TCHAR buffer [256] = { 0 }; + if (GetLocaleInfo (locale, key, buffer, 255) > 0) + return buffer; + + return defaultValue; +} + +String SystemStats::getUserLanguage() { return getLocaleValue (LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, "en"); } +String SystemStats::getUserRegion() { return getLocaleValue (LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, "US"); } + +String SystemStats::getDisplayLanguage() +{ + DynamicLibrary dll ("kernel32.dll"); + BEAST_LOAD_WINAPI_FUNCTION (dll, GetUserDefaultUILanguage, getUserDefaultUILanguage, LANGID, (void)) + + if (getUserDefaultUILanguage != nullptr) + return getLocaleValue (MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT), LOCALE_SISO639LANGNAME, "en"); + + return "en"; +} diff --git a/modules/beast_core/native/beast_win32_Threads.cpp b/modules/beast_core/native/beast_win32_Threads.cpp new file mode 100644 index 0000000000..7b9331bb84 --- /dev/null +++ b/modules/beast_core/native/beast_win32_Threads.cpp @@ -0,0 +1,634 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +HWND beast_messageWindowHandle = 0; // (this is used by other parts of the codebase) + +//============================================================================== +#if ! BEAST_USE_INTRINSICS +// In newer compilers, the inline versions of these are used (in beast_Atomic.h), but in +// older ones we have to actually call the ops as win32 functions.. +long beast_InterlockedExchange (volatile long* a, long b) noexcept { return InterlockedExchange (a, b); } +long beast_InterlockedIncrement (volatile long* a) noexcept { return InterlockedIncrement (a); } +long beast_InterlockedDecrement (volatile long* a) noexcept { return InterlockedDecrement (a); } +long beast_InterlockedExchangeAdd (volatile long* a, long b) noexcept { return InterlockedExchangeAdd (a, b); } +long beast_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept { return InterlockedCompareExchange (a, b, c); } + +__int64 beast_InterlockedCompareExchange64 (volatile __int64* value, __int64 newValue, __int64 valueToCompare) noexcept +{ + jassertfalse; // This operation isn't available in old MS compiler versions! + + __int64 oldValue = *value; + if (oldValue == valueToCompare) + *value = newValue; + + return oldValue; +} + +#endif + +//============================================================================== +CriticalSection::CriticalSection() noexcept +{ + // (just to check the MS haven't changed this structure and broken things...) + #if BEAST_VC7_OR_EARLIER + static_bassert (sizeof (CRITICAL_SECTION) <= 24); + #else + static_bassert (sizeof (CRITICAL_SECTION) <= sizeof (internal)); + #endif + + InitializeCriticalSection ((CRITICAL_SECTION*) internal); +} + +CriticalSection::~CriticalSection() noexcept +{ + DeleteCriticalSection ((CRITICAL_SECTION*) internal); +} + +void CriticalSection::enter() const noexcept +{ + EnterCriticalSection ((CRITICAL_SECTION*) internal); +} + +bool CriticalSection::tryEnter() const noexcept +{ + return TryEnterCriticalSection ((CRITICAL_SECTION*) internal) != FALSE; +} + +void CriticalSection::exit() const noexcept +{ + LeaveCriticalSection ((CRITICAL_SECTION*) internal); +} + +//============================================================================== +WaitableEvent::WaitableEvent (const bool manualReset) noexcept + : internal (CreateEvent (0, manualReset ? TRUE : FALSE, FALSE, 0)) +{ +} + +WaitableEvent::~WaitableEvent() noexcept +{ + CloseHandle (internal); +} + +bool WaitableEvent::wait (const int timeOutMillisecs) const noexcept +{ + return WaitForSingleObject (internal, (DWORD) timeOutMillisecs) == WAIT_OBJECT_0; +} + +void WaitableEvent::signal() const noexcept +{ + SetEvent (internal); +} + +void WaitableEvent::reset() const noexcept +{ + ResetEvent (internal); +} + +//============================================================================== +void BEAST_API beast_threadEntryPoint (void*); + +static unsigned int __stdcall threadEntryProc (void* userData) +{ + if (beast_messageWindowHandle != 0) + AttachThreadInput (GetWindowThreadProcessId (beast_messageWindowHandle, 0), + GetCurrentThreadId(), TRUE); + + beast_threadEntryPoint (userData); + + _endthreadex (0); + return 0; +} + +void Thread::launchThread() +{ + unsigned int newThreadId; + threadHandle = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId); + threadId = (ThreadID) newThreadId; +} + +void Thread::closeThreadHandle() +{ + CloseHandle ((HANDLE) threadHandle); + threadId = 0; + threadHandle = 0; +} + +void Thread::killThread() +{ + if (threadHandle != 0) + { + #if BEAST_DEBUG + OutputDebugStringA ("** Warning - Forced thread termination **\n"); + #endif + TerminateThread (threadHandle, 0); + } +} + +void Thread::setCurrentThreadName (const String& name) +{ + #if BEAST_DEBUG && BEAST_MSVC + struct + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + } info; + + info.dwType = 0x1000; + info.szName = name.toUTF8(); + info.dwThreadID = GetCurrentThreadId(); + info.dwFlags = 0; + + __try + { + RaiseException (0x406d1388 /*MS_VC_EXCEPTION*/, 0, sizeof (info) / sizeof (ULONG_PTR), (ULONG_PTR*) &info); + } + __except (EXCEPTION_CONTINUE_EXECUTION) + {} + #else + (void) name; + #endif +} + +Thread::ThreadID Thread::getCurrentThreadId() +{ + return (ThreadID) (pointer_sized_int) GetCurrentThreadId(); +} + +bool Thread::setThreadPriority (void* handle, int priority) +{ + int pri = THREAD_PRIORITY_TIME_CRITICAL; + + if (priority < 1) pri = THREAD_PRIORITY_IDLE; + else if (priority < 2) pri = THREAD_PRIORITY_LOWEST; + else if (priority < 5) pri = THREAD_PRIORITY_BELOW_NORMAL; + else if (priority < 7) pri = THREAD_PRIORITY_NORMAL; + else if (priority < 9) pri = THREAD_PRIORITY_ABOVE_NORMAL; + else if (priority < 10) pri = THREAD_PRIORITY_HIGHEST; + + if (handle == 0) + handle = GetCurrentThread(); + + return SetThreadPriority (handle, pri) != FALSE; +} + +void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) +{ + SetThreadAffinityMask (GetCurrentThread(), affinityMask); +} + +//============================================================================== +struct SleepEvent +{ + SleepEvent() noexcept + : handle (CreateEvent (nullptr, FALSE, FALSE, + #if BEAST_DEBUG + _T("BEAST Sleep Event"))) + #else + nullptr)) + #endif + {} + + ~SleepEvent() noexcept + { + CloseHandle (handle); + handle = 0; + } + + HANDLE handle; +}; + +static SleepEvent sleepEvent; + +void BEAST_CALLTYPE Thread::sleep (const int millisecs) +{ + if (millisecs >= 10 || sleepEvent.handle == 0) + { + Sleep ((DWORD) millisecs); + } + else + { + // unlike Sleep() this is guaranteed to return to the current thread after + // the time expires, so we'll use this for short waits, which are more likely + // to need to be accurate + WaitForSingleObject (sleepEvent.handle, (DWORD) millisecs); + } +} + +void Thread::yield() +{ + Sleep (0); +} + +//============================================================================== +static int lastProcessPriority = -1; + +// called by WindowDriver because Windows does weird things to process priority +// when you swap apps, and this forces an update when the app is brought to the front. +void beast_repeatLastProcessPriority() +{ + if (lastProcessPriority >= 0) // (avoid changing this if it's not been explicitly set by the app..) + { + DWORD p; + + switch (lastProcessPriority) + { + case Process::LowPriority: p = IDLE_PRIORITY_CLASS; break; + case Process::NormalPriority: p = NORMAL_PRIORITY_CLASS; break; + case Process::HighPriority: p = HIGH_PRIORITY_CLASS; break; + case Process::RealtimePriority: p = REALTIME_PRIORITY_CLASS; break; + default: jassertfalse; return; // bad priority value + } + + SetPriorityClass (GetCurrentProcess(), p); + } +} + +void Process::setPriority (ProcessPriority prior) +{ + if (lastProcessPriority != (int) prior) + { + lastProcessPriority = (int) prior; + beast_repeatLastProcessPriority(); + } +} + +BEAST_API bool BEAST_CALLTYPE beast_isRunningUnderDebugger() +{ + return IsDebuggerPresent() != FALSE; +} + +bool BEAST_CALLTYPE Process::isRunningUnderDebugger() +{ + return beast_isRunningUnderDebugger(); +} + +static void* currentModuleHandle = nullptr; + +void* Process::getCurrentModuleInstanceHandle() noexcept +{ + if (currentModuleHandle == nullptr) + currentModuleHandle = GetModuleHandleA (nullptr); + + return currentModuleHandle; +} + +void Process::setCurrentModuleInstanceHandle (void* const newHandle) noexcept +{ + currentModuleHandle = newHandle; +} + +void Process::raisePrivilege() +{ + jassertfalse; // xxx not implemented +} + +void Process::lowerPrivilege() +{ + jassertfalse; // xxx not implemented +} + +void Process::terminate() +{ + #if BEAST_MSVC && BEAST_CHECK_MEMORY_LEAKS + _CrtDumpMemoryLeaks(); + #endif + + // bullet in the head in case there's a problem shutting down.. + ExitProcess (0); +} + +bool beast_isRunningInWine() +{ + HMODULE ntdll = GetModuleHandleA ("ntdll"); + return ntdll != 0 && GetProcAddress (ntdll, "wine_get_version") != nullptr; +} + +//============================================================================== +bool DynamicLibrary::open (const String& name) +{ + close(); + + BEAST_TRY + { + handle = LoadLibrary (name.toWideCharPointer()); + } + BEAST_CATCH_ALL + + return handle != nullptr; +} + +void DynamicLibrary::close() +{ + BEAST_TRY + { + if (handle != nullptr) + { + FreeLibrary ((HMODULE) handle); + handle = nullptr; + } + } + BEAST_CATCH_ALL +} + +void* DynamicLibrary::getFunction (const String& functionName) noexcept +{ + return handle != nullptr ? (void*) GetProcAddress ((HMODULE) handle, functionName.toUTF8()) // (void* cast is required for mingw) + : nullptr; +} + + +//============================================================================== +class InterProcessLock::Pimpl +{ +public: + Pimpl (String name, const int timeOutMillisecs) + : handle (0), refCount (1) + { + name = name.replaceCharacter ('\\', '/'); + handle = CreateMutexW (0, TRUE, ("Global\\" + name).toWideCharPointer()); + + // Not 100% sure why a global mutex sometimes can't be allocated, but if it fails, fall back to + // a local one. (A local one also sometimes fails on other machines so neither type appears to be + // universally reliable) + if (handle == 0) + handle = CreateMutexW (0, TRUE, ("Local\\" + name).toWideCharPointer()); + + if (handle != 0 && GetLastError() == ERROR_ALREADY_EXISTS) + { + if (timeOutMillisecs == 0) + { + close(); + return; + } + + switch (WaitForSingleObject (handle, timeOutMillisecs < 0 ? INFINITE : timeOutMillisecs)) + { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + break; + + case WAIT_TIMEOUT: + default: + close(); + break; + } + } + } + + ~Pimpl() + { + close(); + } + + void close() + { + if (handle != 0) + { + ReleaseMutex (handle); + CloseHandle (handle); + handle = 0; + } + } + + HANDLE handle; + int refCount; +}; + +InterProcessLock::InterProcessLock (const String& name_) + : name (name_) +{ +} + +InterProcessLock::~InterProcessLock() +{ +} + +bool InterProcessLock::enter (const int timeOutMillisecs) +{ + const ScopedLock sl (lock); + + if (pimpl == nullptr) + { + pimpl = new Pimpl (name, timeOutMillisecs); + + if (pimpl->handle == 0) + pimpl = nullptr; + } + else + { + pimpl->refCount++; + } + + return pimpl != nullptr; +} + +void InterProcessLock::exit() +{ + const ScopedLock sl (lock); + + // Trying to release the lock too many times! + bassert (pimpl != nullptr); + + if (pimpl != nullptr && --(pimpl->refCount) == 0) + pimpl = nullptr; +} + +//============================================================================== +class ChildProcess::ActiveProcess +{ +public: + ActiveProcess (const String& command) + : ok (false), readPipe (0), writePipe (0) + { + SECURITY_ATTRIBUTES securityAtts = { 0 }; + securityAtts.nLength = sizeof (securityAtts); + securityAtts.bInheritHandle = TRUE; + + if (CreatePipe (&readPipe, &writePipe, &securityAtts, 0) + && SetHandleInformation (readPipe, HANDLE_FLAG_INHERIT, 0)) + { + STARTUPINFOW startupInfo = { 0 }; + startupInfo.cb = sizeof (startupInfo); + startupInfo.hStdError = writePipe; + startupInfo.hStdOutput = writePipe; + startupInfo.dwFlags = STARTF_USESTDHANDLES; + + ok = CreateProcess (nullptr, const_cast (command.toWideCharPointer()), + nullptr, nullptr, TRUE, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + nullptr, nullptr, &startupInfo, &processInfo) != FALSE; + } + } + + ~ActiveProcess() + { + if (ok) + { + CloseHandle (processInfo.hThread); + CloseHandle (processInfo.hProcess); + } + + if (readPipe != 0) + CloseHandle (readPipe); + + if (writePipe != 0) + CloseHandle (writePipe); + } + + bool isRunning() const + { + return WaitForSingleObject (processInfo.hProcess, 0) != WAIT_OBJECT_0; + } + + int read (void* dest, int numNeeded) const + { + int total = 0; + + while (ok && numNeeded > 0) + { + DWORD available = 0; + + if (! PeekNamedPipe ((HANDLE) readPipe, nullptr, 0, nullptr, &available, nullptr)) + break; + + const int numToDo = bmin ((int) available, numNeeded); + + if (available == 0) + { + if (! isRunning()) + break; + + Thread::yield(); + } + else + { + DWORD numRead = 0; + if (! ReadFile ((HANDLE) readPipe, dest, numToDo, &numRead, nullptr)) + break; + + total += numRead; + dest = addBytesToPointer (dest, numRead); + numNeeded -= numRead; + } + } + + return total; + } + + bool killProcess() const + { + return TerminateProcess (processInfo.hProcess, 0) != FALSE; + } + + bool ok; + +private: + HANDLE readPipe, writePipe; + PROCESS_INFORMATION processInfo; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveProcess) +}; + +bool ChildProcess::start (const String& command) +{ + activeProcess = new ActiveProcess (command); + + if (! activeProcess->ok) + activeProcess = nullptr; + + return activeProcess != nullptr; +} + +bool ChildProcess::start (const StringArray& args) +{ + return start (args.joinIntoString (" ")); +} + +bool ChildProcess::isRunning() const +{ + return activeProcess != nullptr && activeProcess->isRunning(); +} + +int ChildProcess::readProcessOutput (void* dest, int numBytes) +{ + return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; +} + +bool ChildProcess::kill() +{ + return activeProcess == nullptr || activeProcess->killProcess(); +} + +//============================================================================== +struct HighResolutionTimer::Pimpl +{ + Pimpl (HighResolutionTimer& t) noexcept : owner (t), periodMs (0) + { + } + + ~Pimpl() + { + bassert (periodMs == 0); + } + + void start (int newPeriod) + { + if (newPeriod != periodMs) + { + stop(); + periodMs = newPeriod; + + TIMECAPS tc; + if (timeGetDevCaps (&tc, sizeof (tc)) == TIMERR_NOERROR) + { + const int actualPeriod = blimit ((int) tc.wPeriodMin, (int) tc.wPeriodMax, newPeriod); + + timerID = timeSetEvent (actualPeriod, tc.wPeriodMin, callbackFunction, (DWORD_PTR) this, + TIME_PERIODIC | TIME_CALLBACK_FUNCTION | 0x100 /*TIME_KILL_SYNCHRONOUS*/); + } + } + } + + void stop() + { + periodMs = 0; + timeKillEvent (timerID); + } + + HighResolutionTimer& owner; + int periodMs; + +private: + unsigned int timerID; + + static void __stdcall callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR) + { + if (Pimpl* const timer = reinterpret_cast (userInfo)) + if (timer->periodMs != 0) + timer->owner.hiResTimerCallback(); + } + + BEAST_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/modules/beast_core/native/java/BeastAppActivity.java b/modules/beast_core/native/java/BeastAppActivity.java new file mode 100644 index 0000000000..000871b0ee --- /dev/null +++ b/modules/beast_core/native/java/BeastAppActivity.java @@ -0,0 +1,697 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +package com.beast; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.graphics.*; +import android.opengl.*; +import android.text.ClipboardManager; +import android.text.InputType; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.HttpURLConnection; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import android.media.MediaScannerConnection; +import android.media.MediaScannerConnection.MediaScannerConnectionClient; + +//============================================================================== +public final class BeastAppActivity extends Activity +{ + //============================================================================== + static + { + System.loadLibrary ("beast_jni"); + } + + @Override + public final void onCreate (Bundle savedInstanceState) + { + super.onCreate (savedInstanceState); + + viewHolder = new ViewHolder (this); + setContentView (viewHolder); + + setVolumeControlStream (AudioManager.STREAM_MUSIC); + } + + @Override + protected final void onDestroy() + { + quitApp(); + super.onDestroy(); + } + + @Override + protected final void onPause() + { + if (viewHolder != null) + viewHolder.onPause(); + + suspendApp(); + super.onPause(); + } + + @Override + protected final void onResume() + { + super.onResume(); + + if (viewHolder != null) + viewHolder.onResume(); + + resumeApp(); + } + + @Override + public void onConfigurationChanged (Configuration cfg) + { + super.onConfigurationChanged (cfg); + setContentView (viewHolder); + } + + private void callAppLauncher() + { + launchApp (getApplicationInfo().publicSourceDir, + getApplicationInfo().dataDir); + } + + //============================================================================== + private native void launchApp (String appFile, String appDataDir); + private native void quitApp(); + private native void suspendApp(); + private native void resumeApp(); + private native void setScreenSize (int screenWidth, int screenHeight); + + //============================================================================== + public native void deliverMessage (long value); + private android.os.Handler messageHandler = new android.os.Handler(); + + public final void postMessage (long value) + { + messageHandler.post (new MessageCallback (value)); + } + + private final class MessageCallback implements Runnable + { + public MessageCallback (long value_) { value = value_; } + public final void run() { deliverMessage (value); } + + private long value; + } + + //============================================================================== + private ViewHolder viewHolder; + + public final ComponentPeerView createNewView (boolean opaque) + { + ComponentPeerView v = new ComponentPeerView (this, opaque); + viewHolder.addView (v); + return v; + } + + public final void deleteView (ComponentPeerView view) + { + ViewGroup group = (ViewGroup) (view.getParent()); + + if (group != null) + group.removeView (view); + } + + final class ViewHolder extends ViewGroup + { + public ViewHolder (Context context) + { + super (context); + setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS); + setFocusable (false); + } + + protected final void onLayout (boolean changed, int left, int top, int right, int bottom) + { + setScreenSize (getWidth(), getHeight()); + + if (isFirstResize) + { + isFirstResize = false; + callAppLauncher(); + } + } + + public final void onPause() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof ComponentPeerView) + ((ComponentPeerView) v).onPause(); + } + } + + public final void onResume() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof ComponentPeerView) + ((ComponentPeerView) v).onResume(); + } + } + + private boolean isFirstResize = true; + } + + public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom) + { + canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE); + } + + //============================================================================== + public final String getClipboardContent() + { + ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE); + return clipboard.getText().toString(); + } + + public final void setClipboardContent (String newText) + { + ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE); + clipboard.setText (newText); + } + + //============================================================================== + public final void showMessageBox (String title, String message, final long callback) + { + AlertDialog.Builder builder = new AlertDialog.Builder (this); + builder.setTitle (title) + .setMessage (message) + .setCancelable (true) + .setPositiveButton ("OK", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + BeastAppActivity.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public final void showOkCancelBox (String title, String message, final long callback) + { + AlertDialog.Builder builder = new AlertDialog.Builder (this); + builder.setTitle (title) + .setMessage (message) + .setCancelable (true) + .setPositiveButton ("OK", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + BeastAppActivity.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + BeastAppActivity.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public final void showYesNoCancelBox (String title, String message, final long callback) + { + AlertDialog.Builder builder = new AlertDialog.Builder (this); + builder.setTitle (title) + .setMessage (message) + .setCancelable (true) + .setPositiveButton ("Yes", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + BeastAppActivity.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("No", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + BeastAppActivity.this.alertDismissed (callback, 2); + } + }) + .setNeutralButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + BeastAppActivity.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public native void alertDismissed (long callback, int id); + + //============================================================================== + public final class ComponentPeerView extends ViewGroup + implements View.OnFocusChangeListener + { + public ComponentPeerView (Context context, boolean opaque_) + { + super (context); + setWillNotDraw (false); + opaque = opaque_; + + setFocusable (true); + setFocusableInTouchMode (true); + setOnFocusChangeListener (this); + requestFocus(); + } + + //============================================================================== + private native void handlePaint (Canvas canvas); + + @Override + public void draw (Canvas canvas) + { + super.draw (canvas); + handlePaint (canvas); + } + + @Override + public boolean isOpaque() + { + return opaque; + } + + private boolean opaque; + + //============================================================================== + private native void handleMouseDown (int index, float x, float y, long time); + private native void handleMouseDrag (int index, float x, float y, long time); + private native void handleMouseUp (int index, float x, float y, long time); + + @Override + public boolean onTouchEvent (MotionEvent event) + { + int action = event.getAction(); + long time = event.getEventTime(); + + switch (action & MotionEvent.ACTION_MASK) + { + case MotionEvent.ACTION_DOWN: + handleMouseDown (event.getPointerId(0), event.getX(), event.getY(), time); + return true; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + handleMouseUp (event.getPointerId(0), event.getX(), event.getY(), time); + return true; + + case MotionEvent.ACTION_MOVE: + { + int n = event.getPointerCount(); + for (int i = 0; i < n; ++i) + handleMouseDrag (event.getPointerId(i), event.getX(i), event.getY(i), time); + + return true; + } + + case MotionEvent.ACTION_POINTER_UP: + { + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + handleMouseUp (event.getPointerId(i), event.getX(i), event.getY(i), time); + return true; + } + + case MotionEvent.ACTION_POINTER_DOWN: + { + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + handleMouseDown (event.getPointerId(i), event.getX(i), event.getY(i), time); + return true; + } + + default: + break; + } + + return false; + } + + //============================================================================== + private native void handleKeyDown (int keycode, int textchar); + private native void handleKeyUp (int keycode, int textchar); + + public void showKeyboard (boolean shouldShow) + { + InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE); + + if (imm != null) + { + if (shouldShow) + imm.showSoftInput (this, InputMethodManager.SHOW_FORCED); + else + imm.hideSoftInputFromWindow (getWindowToken(), 0); + } + } + + @Override + public boolean onKeyDown (int keyCode, KeyEvent event) + { + handleKeyDown (keyCode, event.getUnicodeChar()); + return true; + } + + @Override + public boolean onKeyUp (int keyCode, KeyEvent event) + { + handleKeyUp (keyCode, event.getUnicodeChar()); + return true; + } + + // this is here to make keyboard entry work on a Galaxy Tab2 10.1 + @Override + public InputConnection onCreateInputConnection (EditorInfo outAttrs) + { + outAttrs.actionLabel = ""; + outAttrs.hintText = ""; + outAttrs.initialCapsMode = 0; + outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; + outAttrs.label = ""; + outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI; + outAttrs.inputType = InputType.TYPE_NULL; + + return new BaseInputConnection (this, false); + } + + //============================================================================== + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) + { + super.onSizeChanged (w, h, oldw, oldh); + viewSizeChanged(); + } + + @Override + protected void onLayout (boolean changed, int left, int top, int right, int bottom) + { + for (int i = getChildCount(); --i >= 0;) + requestTransparentRegion (getChildAt (i)); + } + + private native void viewSizeChanged(); + + @Override + public void onFocusChange (View v, boolean hasFocus) + { + if (v == this) + focusChanged (hasFocus); + } + + private native void focusChanged (boolean hasFocus); + + public void setViewName (String newName) {} + + public boolean isVisible() { return getVisibility() == VISIBLE; } + public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); } + + public boolean containsPoint (int x, int y) + { + return true; //xxx needs to check overlapping views + } + + public final void onPause() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof OpenGLView) + ((OpenGLView) v).onPause(); + } + } + + public final void onResume() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof OpenGLView) + ((OpenGLView) v).onResume(); + } + } + + public OpenGLView createGLView() + { + OpenGLView glView = new OpenGLView (getContext()); + addView (glView); + return glView; + } + } + + //============================================================================== + public final class OpenGLView extends GLSurfaceView + implements GLSurfaceView.Renderer + { + OpenGLView (Context context) + { + super (context); + setEGLContextClientVersion (2); + setRenderer (this); + setRenderMode (RENDERMODE_WHEN_DIRTY); + } + + @Override + public void onSurfaceCreated (GL10 unused, EGLConfig config) + { + contextCreated(); + } + + @Override + public void onSurfaceChanged (GL10 unused, int width, int height) + { + contextChangedSize(); + } + + @Override + public void onDrawFrame (GL10 unused) + { + render(); + } + + private native void contextCreated(); + private native void contextChangedSize(); + private native void render(); + } + + //============================================================================== + public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds) + { + Path p = new Path(); + paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p); + + RectF boundsF = new RectF(); + p.computeBounds (boundsF, true); + matrix.mapRect (boundsF); + + boundsF.roundOut (bounds); + bounds.left--; + bounds.right++; + + final int w = bounds.width(); + final int h = Math.max (1, bounds.height()); + + Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888); + + Canvas c = new Canvas (bm); + matrix.postTranslate (-bounds.left, -bounds.top); + c.setMatrix (matrix); + c.drawPath (p, paint); + + final int sizeNeeded = w * h; + if (cachedRenderArray.length < sizeNeeded) + cachedRenderArray = new int [sizeNeeded]; + + bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h); + bm.recycle(); + return cachedRenderArray; + } + + private int[] cachedRenderArray = new int [256]; + + //============================================================================== + public static class HTTPStream + { + public HTTPStream (HttpURLConnection connection_) throws IOException + { + connection = connection_; + inputStream = new BufferedInputStream (connection.getInputStream()); + } + + public final void release() + { + try + { + inputStream.close(); + } + catch (IOException e) + {} + + connection.disconnect(); + } + + public final int read (byte[] buffer, int numBytes) + { + int num = 0; + + try + { + num = inputStream.read (buffer, 0, numBytes); + } + catch (IOException e) + {} + + if (num > 0) + position += num; + + return num; + } + + public final long getPosition() { return position; } + public final long getTotalLength() { return -1; } + public final boolean isExhausted() { return false; } + public final boolean setPosition (long newPos) { return false; } + + private HttpURLConnection connection; + private InputStream inputStream; + private long position; + } + + public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData, + String headers, int timeOutMs, + java.lang.StringBuffer responseHeaders) + { + try + { + HttpURLConnection connection = (HttpURLConnection) (new URL (address).openConnection()); + + if (connection != null) + { + try + { + if (isPost) + { + connection.setConnectTimeout (timeOutMs); + connection.setDoOutput (true); + connection.setChunkedStreamingMode (0); + + OutputStream out = connection.getOutputStream(); + out.write (postData); + out.flush(); + } + + return new HTTPStream (connection); + } + catch (Throwable e) + { + connection.disconnect(); + } + } + } + catch (Throwable e) + {} + + return null; + } + + public final void launchURL (String url) + { + startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url))); + } + + public static final String getLocaleValue (boolean isRegion) + { + java.util.Locale locale = java.util.Locale.getDefault(); + + return isRegion ? locale.getDisplayCountry (java.util.Locale.US) + : locale.getDisplayLanguage (java.util.Locale.US); + } + + //============================================================================== + private final class SingleMediaScanner implements MediaScannerConnectionClient + { + public SingleMediaScanner (Context context, String filename) + { + file = filename; + msc = new MediaScannerConnection (context, this); + msc.connect(); + } + + @Override + public void onMediaScannerConnected() + { + msc.scanFile (file, null); + } + + @Override + public void onScanCompleted (String path, Uri uri) + { + msc.disconnect(); + } + + private MediaScannerConnection msc; + private String file; + } + + public final void scanFile (String filename) + { + new SingleMediaScanner (this, filename); + } +} diff --git a/modules/beast_core/network/beast_IPAddress.cpp b/modules/beast_core/network/beast_IPAddress.cpp new file mode 100644 index 0000000000..2a15c9315d --- /dev/null +++ b/modules/beast_core/network/beast_IPAddress.cpp @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +IPAddress::IPAddress() noexcept +{ + address[0] = 0; address[1] = 0; + address[2] = 0; address[3] = 0; +} + +IPAddress::IPAddress (const uint8 bytes[4]) noexcept +{ + address[0] = bytes[0]; address[1] = bytes[1]; + address[2] = bytes[2]; address[3] = bytes[3]; +} + +IPAddress::IPAddress (uint8 a0, uint8 a1, uint8 a2, uint8 a3) noexcept +{ + address[0] = a0; address[1] = a1; + address[2] = a2; address[3] = a3; +} + +IPAddress::IPAddress (uint32 n) noexcept +{ + address[0] = (n >> 24); + address[1] = (n >> 16) & 255; + address[2] = (n >> 8) & 255; + address[3] = (n & 255); +} + +IPAddress::IPAddress (const String& adr) +{ + StringArray tokens; + tokens.addTokens (adr, ".", String::empty); + + for (int i = 0; i < 4; ++i) + address[i] = (uint8) tokens[i].getIntValue(); +} + +String IPAddress::toString() const +{ + String s ((int) address[0]); + + for (int i = 1; i < 4; ++i) + s << '.' << (int) address[i]; + + return s; +} + +IPAddress IPAddress::any() noexcept { return IPAddress(); } +IPAddress IPAddress::broadcast() noexcept { return IPAddress (255, 255, 255, 255); } +IPAddress IPAddress::local() noexcept { return IPAddress (127, 0, 0, 1); } + +bool IPAddress::operator== (const IPAddress& other) const noexcept +{ + return address[0] == other.address[0] + && address[1] == other.address[1] + && address[2] == other.address[2] + && address[3] == other.address[3]; +} + +bool IPAddress::operator!= (const IPAddress& other) const noexcept +{ + return ! operator== (other); +} + +#if ! BEAST_WINDOWS +static void addAddress (const sockaddr_in* addr_in, Array& result) +{ + in_addr_t addr = addr_in->sin_addr.s_addr; + + if (addr != INADDR_NONE) + result.addIfNotAlreadyThere (IPAddress (ntohl (addr))); +} + +static void findIPAddresses (int sock, Array& result) +{ + ifconf cfg; + HeapBlock buffer; + size_t bufferSize = 1024; + + do + { + bufferSize *= 2; + buffer.calloc (bufferSize); + + cfg.ifc_len = bufferSize; + cfg.ifc_buf = buffer; + + if (ioctl (sock, SIOCGIFCONF, &cfg) < 0 && errno != EINVAL) + return; + + } while (bufferSize < cfg.ifc_len + 2 * (IFNAMSIZ + sizeof (struct sockaddr_in6))); + + #if BEAST_MAC || BEAST_IOS + while (cfg.ifc_len >= (int) (IFNAMSIZ + sizeof (struct sockaddr_in))) + { + if (cfg.ifc_req->ifr_addr.sa_family == AF_INET) // Skip non-internet addresses + addAddress ((const sockaddr_in*) &cfg.ifc_req->ifr_addr, result); + + cfg.ifc_len -= IFNAMSIZ + cfg.ifc_req->ifr_addr.sa_len; + cfg.ifc_buf += IFNAMSIZ + cfg.ifc_req->ifr_addr.sa_len; + } + #else + for (int i = 0; i < cfg.ifc_len / sizeof (struct ifreq); ++i) + { + const ifreq& item = cfg.ifc_req[i]; + + if (item.ifr_addr.sa_family == AF_INET) + addAddress ((const sockaddr_in*) &item.ifr_addr, result); + } + #endif +} + +void IPAddress::findAllAddresses (Array& result) +{ + const int sock = socket (AF_INET, SOCK_DGRAM, 0); // a dummy socket to execute the IO control + + if (sock >= 0) + { + findIPAddresses (sock, result); + ::close (sock); + } +} +#endif diff --git a/modules/beast_core/network/beast_IPAddress.h b/modules/beast_core/network/beast_IPAddress.h new file mode 100644 index 0000000000..9232365d33 --- /dev/null +++ b/modules/beast_core/network/beast_IPAddress.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_IPADDRESS_BEASTHEADER +#define BEAST_IPADDRESS_BEASTHEADER + + +//============================================================================== +/** + An IPV4 address. +*/ +class BEAST_API IPAddress +{ +public: + //============================================================================== + /** Populates a list of all the IP addresses that this machine is using. */ + static void findAllAddresses (Array& results); + + //============================================================================== + /** Creates a null address (0.0.0.0). */ + IPAddress() noexcept; + + /** Creates an address from 4 bytes. */ + explicit IPAddress (const uint8 bytes[4]) noexcept; + + /** Creates an address from 4 bytes. */ + IPAddress (uint8 address1, uint8 address2, uint8 address3, uint8 address4) noexcept; + + /** Creates an address from a packed 32-bit integer, where the MSB is + the first number in the address, and the LSB is the last. + */ + explicit IPAddress (uint32 asNativeEndian32Bit) noexcept; + + /** Parses a string IP address of the form "a.b.c.d". */ + explicit IPAddress (const String& address); + + /** Returns a dot-separated string in the form "1.2.3.4" */ + String toString() const; + + /** Returns an address meaning "any" (0.0.0.0) */ + static IPAddress any() noexcept; + + /** Returns an address meaning "broadcast" (255.255.255.255) */ + static IPAddress broadcast() noexcept; + + /** Returns an address meaning "localhost" (127.0.0.1) */ + static IPAddress local() noexcept; + + bool operator== (const IPAddress& other) const noexcept; + bool operator!= (const IPAddress& other) const noexcept; + + /** The elements of the IP address. */ + uint8 address[4]; +}; + + +#endif // BEAST_IPADDRESS_BEASTHEADER diff --git a/modules/beast_core/network/beast_MACAddress.cpp b/modules/beast_core/network/beast_MACAddress.cpp new file mode 100644 index 0000000000..d9de043f76 --- /dev/null +++ b/modules/beast_core/network/beast_MACAddress.cpp @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +MACAddress::MACAddress() +{ + zeromem (address, sizeof (address)); +} + +MACAddress::MACAddress (const MACAddress& other) +{ + memcpy (address, other.address, sizeof (address)); +} + +MACAddress& MACAddress::operator= (const MACAddress& other) +{ + memcpy (address, other.address, sizeof (address)); + return *this; +} + +MACAddress::MACAddress (const uint8 bytes[6]) +{ + memcpy (address, bytes, sizeof (address)); +} + +String MACAddress::toString() const +{ + String s; + + for (size_t i = 0; i < sizeof (address); ++i) + { + s << String::toHexString ((int) address[i]).paddedLeft ('0', 2); + + if (i < sizeof (address) - 1) + s << '-'; + } + + return s; +} + +int64 MACAddress::toInt64() const noexcept +{ + int64 n = 0; + + for (int i = (int) sizeof (address); --i >= 0;) + n = (n << 8) | address[i]; + + return n; +} + +bool MACAddress::isNull() const noexcept { return toInt64() == 0; } + +bool MACAddress::operator== (const MACAddress& other) const noexcept { return memcmp (address, other.address, sizeof (address)) == 0; } +bool MACAddress::operator!= (const MACAddress& other) const noexcept { return ! operator== (other); } diff --git a/modules/beast_core/network/beast_MACAddress.h b/modules/beast_core/network/beast_MACAddress.h new file mode 100644 index 0000000000..6f9fb945f2 --- /dev/null +++ b/modules/beast_core/network/beast_MACAddress.h @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MACADDRESS_BEASTHEADER +#define BEAST_MACADDRESS_BEASTHEADER + +#include "../containers/beast_Array.h" + + +//============================================================================== +/** + A wrapper for a streaming (TCP) socket. + + This allows low-level use of sockets; for an easier-to-use messaging layer on top of + sockets, you could also try the InterprocessConnection class. + + @see DatagramSocket, InterprocessConnection, InterprocessConnectionServer +*/ +class BEAST_API MACAddress +{ +public: + //============================================================================== + /** Populates a list of the MAC addresses of all the available network cards. */ + static void findAllAddresses (Array& results); + + //============================================================================== + /** Creates a null address (00-00-00-00-00-00). */ + MACAddress(); + + /** Creates a copy of another address. */ + MACAddress (const MACAddress& other); + + /** Creates a copy of another address. */ + MACAddress& operator= (const MACAddress& other); + + /** Creates an address from 6 bytes. */ + explicit MACAddress (const uint8 bytes[6]); + + /** Returns a pointer to the 6 bytes that make up this address. */ + const uint8* getBytes() const noexcept { return address; } + + /** Returns a dash-separated string in the form "11-22-33-44-55-66" */ + String toString() const; + + /** Returns the address in the lower 6 bytes of an int64. + + This uses a little-endian arrangement, with the first byte of the address being + stored in the least-significant byte of the result value. + */ + int64 toInt64() const noexcept; + + /** Returns true if this address is null (00-00-00-00-00-00). */ + bool isNull() const noexcept; + + bool operator== (const MACAddress& other) const noexcept; + bool operator!= (const MACAddress& other) const noexcept; + + //============================================================================== +private: + uint8 address[6]; +}; + + +#endif // BEAST_MACADDRESS_BEASTHEADER diff --git a/modules/beast_core/network/beast_NamedPipe.cpp b/modules/beast_core/network/beast_NamedPipe.cpp new file mode 100644 index 0000000000..c46cae3b7d --- /dev/null +++ b/modules/beast_core/network/beast_NamedPipe.cpp @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +NamedPipe::NamedPipe() +{ +} + +NamedPipe::~NamedPipe() +{ + close(); +} + +bool NamedPipe::openExisting (const String& pipeName) +{ + close(); + + ScopedWriteLock sl (lock); + currentPipeName = pipeName; + return openInternal (pipeName, false); +} + +bool NamedPipe::isOpen() const +{ + return pimpl != nullptr; +} + +bool NamedPipe::createNewPipe (const String& pipeName) +{ + close(); + + ScopedWriteLock sl (lock); + currentPipeName = pipeName; + return openInternal (pipeName, true); +} + +String NamedPipe::getName() const +{ + return currentPipeName; +} + +// other methods for this class are implemented in the platform-specific files diff --git a/modules/beast_core/network/beast_NamedPipe.h b/modules/beast_core/network/beast_NamedPipe.h new file mode 100644 index 0000000000..ae9c24c5ff --- /dev/null +++ b/modules/beast_core/network/beast_NamedPipe.h @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_NAMEDPIPE_BEASTHEADER +#define BEAST_NAMEDPIPE_BEASTHEADER + +#include "../threads/beast_ReadWriteLock.h" + +//============================================================================== +/** + A cross-process pipe that can have data written to and read from it. + + Two processes can use NamedPipe objects to exchange blocks of data. + + @see InterprocessConnection +*/ +class BEAST_API NamedPipe +{ +public: + //============================================================================== + /** Creates a NamedPipe. */ + NamedPipe(); + + /** Destructor. */ + ~NamedPipe(); + + //============================================================================== + /** Tries to open a pipe that already exists. + Returns true if it succeeds. + */ + bool openExisting (const String& pipeName); + + /** Tries to create a new pipe. + Returns true if it succeeds. + */ + bool createNewPipe (const String& pipeName); + + /** Closes the pipe, if it's open. */ + void close(); + + /** True if the pipe is currently open. */ + bool isOpen() const; + + /** Returns the last name that was used to try to open this pipe. */ + String getName() const; + + //============================================================================== + /** Reads data from the pipe. + + This will block until another thread has written enough data into the pipe to fill + the number of bytes specified, or until another thread calls the cancelPendingReads() + method. + + If the operation fails, it returns -1, otherwise, it will return the number of + bytes read. + + If timeOutMilliseconds is less than zero, it will wait indefinitely, otherwise + this is a maximum timeout for reading from the pipe. + */ + int read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds); + + /** Writes some data to the pipe. + @returns the number of bytes written, or -1 on failure. + */ + int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds); + +private: + //============================================================================== + BEAST_PUBLIC_IN_DLL_BUILD (class Pimpl) + ScopedPointer pimpl; + String currentPipeName; + ReadWriteLock lock; + + bool openInternal (const String& pipeName, const bool createPipe); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NamedPipe) +}; + + +#endif // BEAST_NAMEDPIPE_BEASTHEADER diff --git a/modules/beast_core/network/beast_Socket.cpp b/modules/beast_core/network/beast_Socket.cpp new file mode 100644 index 0000000000..dead36b1b9 --- /dev/null +++ b/modules/beast_core/network/beast_Socket.cpp @@ -0,0 +1,585 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable : 4127 4389 4018) +#endif + +#ifndef AI_NUMERICSERV // (missing in older Mac SDKs) + #define AI_NUMERICSERV 0x1000 +#endif + +#if BEAST_WINDOWS + typedef int beast_socklen_t; + typedef SOCKET SocketHandle; +#else + typedef socklen_t beast_socklen_t; + typedef int SocketHandle; +#endif + +//============================================================================== +namespace SocketHelpers +{ + static void initSockets() + { + #if BEAST_WINDOWS + static bool socketsStarted = false; + + if (! socketsStarted) + { + socketsStarted = true; + + WSADATA wsaData; + const WORD wVersionRequested = MAKEWORD (1, 1); + WSAStartup (wVersionRequested, &wsaData); + } + #endif + } + + static bool resetSocketOptions (const SocketHandle handle, const bool isDatagram, const bool allowBroadcast) noexcept + { + const int sndBufSize = 65536; + const int rcvBufSize = 65536; + const int one = 1; + + return handle > 0 + && setsockopt (handle, SOL_SOCKET, SO_RCVBUF, (const char*) &rcvBufSize, sizeof (rcvBufSize)) == 0 + && setsockopt (handle, SOL_SOCKET, SO_SNDBUF, (const char*) &sndBufSize, sizeof (sndBufSize)) == 0 + && (isDatagram ? ((! allowBroadcast) || setsockopt (handle, SOL_SOCKET, SO_BROADCAST, (const char*) &one, sizeof (one)) == 0) + : (setsockopt (handle, IPPROTO_TCP, TCP_NODELAY, (const char*) &one, sizeof (one)) == 0)); + } + + static bool bindSocketToPort (const SocketHandle handle, const int port) noexcept + { + if (handle <= 0 || port <= 0) + return false; + + struct sockaddr_in servTmpAddr; + zerostruct (servTmpAddr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + servTmpAddr.sin_family = PF_INET; + servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); + servTmpAddr.sin_port = htons ((uint16) port); + + return bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) >= 0; + } + + static int readSocket (const SocketHandle handle, + void* const destBuffer, const int maxBytesToRead, + bool volatile& connected, + const bool blockUntilSpecifiedAmountHasArrived) noexcept + { + int bytesRead = 0; + + while (bytesRead < maxBytesToRead) + { + int bytesThisTime; + + #if BEAST_WINDOWS + bytesThisTime = recv (handle, static_cast (destBuffer) + bytesRead, maxBytesToRead - bytesRead, 0); + #else + while ((bytesThisTime = (int) ::read (handle, addBytesToPointer (destBuffer, bytesRead), (size_t) (maxBytesToRead - bytesRead))) < 0 + && errno == EINTR + && connected) + { + } + #endif + + if (bytesThisTime <= 0 || ! connected) + { + if (bytesRead == 0) + bytesRead = -1; + + break; + } + + bytesRead += bytesThisTime; + + if (! blockUntilSpecifiedAmountHasArrived) + break; + } + + return bytesRead; + } + + static int waitForReadiness (const SocketHandle handle, const bool forReading, const int timeoutMsecs) noexcept + { + struct timeval timeout; + struct timeval* timeoutp; + + if (timeoutMsecs >= 0) + { + timeout.tv_sec = timeoutMsecs / 1000; + timeout.tv_usec = (timeoutMsecs % 1000) * 1000; + timeoutp = &timeout; + } + else + { + timeoutp = 0; + } + + fd_set rset, wset; + FD_ZERO (&rset); + FD_SET (handle, &rset); + FD_ZERO (&wset); + FD_SET (handle, &wset); + + fd_set* const prset = forReading ? &rset : nullptr; + fd_set* const pwset = forReading ? nullptr : &wset; + + #if BEAST_WINDOWS + if (select ((int) handle + 1, prset, pwset, 0, timeoutp) < 0) + return -1; + #else + { + int result; + while ((result = select (handle + 1, prset, pwset, 0, timeoutp)) < 0 + && errno == EINTR) + { + } + + if (result < 0) + return -1; + } + #endif + + { + int opt; + beast_socklen_t len = sizeof (opt); + + if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 + || opt != 0) + return -1; + } + + return FD_ISSET (handle, forReading ? &rset : &wset) ? 1 : 0; + } + + static bool setSocketBlockingState (const SocketHandle handle, const bool shouldBlock) noexcept + { + #if BEAST_WINDOWS + u_long nonBlocking = shouldBlock ? 0 : (u_long) 1; + return ioctlsocket (handle, FIONBIO, &nonBlocking) == 0; + #else + int socketFlags = fcntl (handle, F_GETFL, 0); + + if (socketFlags == -1) + return false; + + if (shouldBlock) + socketFlags &= ~O_NONBLOCK; + else + socketFlags |= O_NONBLOCK; + + return fcntl (handle, F_SETFL, socketFlags) == 0; + #endif + } + + static bool connectSocket (int volatile& handle, + const bool isDatagram, + struct addrinfo** const serverAddress, + const String& hostName, + const int portNumber, + const int timeOutMillisecs) noexcept + { + struct addrinfo hints; + zerostruct (hints); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = isDatagram ? SOCK_DGRAM : SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + struct addrinfo* info = nullptr; + if (getaddrinfo (hostName.toUTF8(), String (portNumber).toUTF8(), &hints, &info) != 0 + || info == nullptr) + return false; + + if (handle < 0) + handle = (int) socket (info->ai_family, info->ai_socktype, 0); + + if (handle < 0) + { + freeaddrinfo (info); + return false; + } + + if (isDatagram) + { + if (*serverAddress != nullptr) + freeaddrinfo (*serverAddress); + + *serverAddress = info; + return true; + } + + setSocketBlockingState (handle, false); + const int result = ::connect (handle, info->ai_addr, (socklen_t) info->ai_addrlen); + freeaddrinfo (info); + + if (result < 0) + { + #if BEAST_WINDOWS + if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) + #else + if (errno == EINPROGRESS) + #endif + { + if (waitForReadiness (handle, false, timeOutMillisecs) != 1) + { + setSocketBlockingState (handle, true); + return false; + } + } + } + + setSocketBlockingState (handle, true); + resetSocketOptions (handle, false, false); + + return true; + } +} + +//============================================================================== +StreamingSocket::StreamingSocket() + : portNumber (0), + handle (-1), + connected (false), + isListener (false) +{ + SocketHelpers::initSockets(); +} + +StreamingSocket::StreamingSocket (const String& hostName_, + const int portNumber_, + const int handle_) + : hostName (hostName_), + portNumber (portNumber_), + handle (handle_), + connected (true), + isListener (false) +{ + SocketHelpers::initSockets(); + SocketHelpers::resetSocketOptions (handle_, false, false); +} + +StreamingSocket::~StreamingSocket() +{ + close(); +} + +//============================================================================== +int StreamingSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) +{ + return (connected && ! isListener) ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, blockUntilSpecifiedAmountHasArrived) + : -1; +} + +int StreamingSocket::write (const void* sourceBuffer, const int numBytesToWrite) +{ + if (isListener || ! connected) + return -1; + + #if BEAST_WINDOWS + return send (handle, (const char*) sourceBuffer, numBytesToWrite, 0); + #else + int result; + + while ((result = (int) ::write (handle, sourceBuffer, (size_t) numBytesToWrite)) < 0 + && errno == EINTR) + { + } + + return result; + #endif +} + +//============================================================================== +int StreamingSocket::waitUntilReady (const bool readyForReading, + const int timeoutMsecs) const +{ + return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) + : -1; +} + +//============================================================================== +bool StreamingSocket::bindToPort (const int port) +{ + return SocketHelpers::bindSocketToPort (handle, port); +} + +bool StreamingSocket::connect (const String& remoteHostName, + const int remotePortNumber, + const int timeOutMillisecs) +{ + if (isListener) + { + jassertfalse; // a listener socket can't connect to another one! + return false; + } + + if (connected) + close(); + + hostName = remoteHostName; + portNumber = remotePortNumber; + isListener = false; + + connected = SocketHelpers::connectSocket (handle, false, nullptr, remoteHostName, + remotePortNumber, timeOutMillisecs); + + if (! (connected && SocketHelpers::resetSocketOptions (handle, false, false))) + { + close(); + return false; + } + + return true; +} + +void StreamingSocket::close() +{ + #if BEAST_WINDOWS + if (handle != SOCKET_ERROR || connected) + closesocket (handle); + + connected = false; + #else + if (connected) + { + connected = false; + + if (isListener) + { + // need to do this to interrupt the accept() function.. + StreamingSocket temp; + temp.connect ("localhost", portNumber, 1000); + } + } + + if (handle != -1) + ::close (handle); + #endif + + hostName = String::empty; + portNumber = 0; + handle = -1; + isListener = false; +} + +//============================================================================== +bool StreamingSocket::createListener (const int newPortNumber, const String& localHostName) +{ + if (connected) + close(); + + hostName = "listener"; + portNumber = newPortNumber; + isListener = true; + + struct sockaddr_in servTmpAddr; + zerostruct (servTmpAddr); + + servTmpAddr.sin_family = PF_INET; + servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); + + if (localHostName.isNotEmpty()) + servTmpAddr.sin_addr.s_addr = ::inet_addr (localHostName.toUTF8()); + + servTmpAddr.sin_port = htons ((uint16) portNumber); + + handle = (int) socket (AF_INET, SOCK_STREAM, 0); + + if (handle < 0) + return false; + + const int reuse = 1; + setsockopt (handle, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuse, sizeof (reuse)); + + if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 + || listen (handle, SOMAXCONN) < 0) + { + close(); + return false; + } + + connected = true; + return true; +} + +StreamingSocket* StreamingSocket::waitForNextConnection() const +{ + bassert (isListener || ! connected); // to call this method, you first have to use createListener() to + // prepare this socket as a listener. + + if (connected && isListener) + { + struct sockaddr_storage address; + beast_socklen_t len = sizeof (address); + const int newSocket = (int) accept (handle, (struct sockaddr*) &address, &len); + + if (newSocket >= 0 && connected) + return new StreamingSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), + portNumber, newSocket); + } + + return nullptr; +} + +bool StreamingSocket::isLocal() const noexcept +{ + return hostName == "127.0.0.1"; +} + + +//============================================================================== +//============================================================================== +DatagramSocket::DatagramSocket (const int localPortNumber, const bool allowBroadcast_) + : portNumber (0), + handle (-1), + connected (true), + allowBroadcast (allowBroadcast_), + serverAddress (nullptr) +{ + SocketHelpers::initSockets(); + + handle = (int) socket (AF_INET, SOCK_DGRAM, 0); + bindToPort (localPortNumber); +} + +DatagramSocket::DatagramSocket (const String& hostName_, const int portNumber_, + const int handle_, const int localPortNumber) + : hostName (hostName_), + portNumber (portNumber_), + handle (handle_), + connected (true), + allowBroadcast (false), + serverAddress (nullptr) +{ + SocketHelpers::initSockets(); + + SocketHelpers::resetSocketOptions (handle_, true, allowBroadcast); + bindToPort (localPortNumber); +} + +DatagramSocket::~DatagramSocket() +{ + close(); + + if (serverAddress != nullptr) + freeaddrinfo (static_cast (serverAddress)); +} + +void DatagramSocket::close() +{ + #if BEAST_WINDOWS + closesocket (handle); + connected = false; + #else + connected = false; + ::close (handle); + #endif + + hostName = String::empty; + portNumber = 0; + handle = -1; +} + +bool DatagramSocket::bindToPort (const int port) +{ + return SocketHelpers::bindSocketToPort (handle, port); +} + +bool DatagramSocket::connect (const String& remoteHostName, + const int remotePortNumber, + const int timeOutMillisecs) +{ + if (connected) + close(); + + hostName = remoteHostName; + portNumber = remotePortNumber; + + connected = SocketHelpers::connectSocket (handle, true, (struct addrinfo**) &serverAddress, + remoteHostName, remotePortNumber, + timeOutMillisecs); + + if (! (connected && SocketHelpers::resetSocketOptions (handle, true, allowBroadcast))) + { + close(); + return false; + } + + return true; +} + +DatagramSocket* DatagramSocket::waitForNextConnection() const +{ + while (waitUntilReady (true, -1) == 1) + { + struct sockaddr_storage address; + beast_socklen_t len = sizeof (address); + char buf[1]; + + if (recvfrom (handle, buf, 0, 0, (struct sockaddr*) &address, &len) > 0) + return new DatagramSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), + ntohs (((struct sockaddr_in*) &address)->sin_port), + -1, -1); + } + + return nullptr; +} + +//============================================================================== +int DatagramSocket::waitUntilReady (const bool readyForReading, + const int timeoutMsecs) const +{ + return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) + : -1; +} + +int DatagramSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) +{ + return connected ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, blockUntilSpecifiedAmountHasArrived) + : -1; +} + +int DatagramSocket::write (const void* sourceBuffer, const int numBytesToWrite) +{ + // You need to call connect() first to set the server address.. + bassert (serverAddress != nullptr && connected); + + return connected ? (int) sendto (handle, (const char*) sourceBuffer, + (size_t) numBytesToWrite, 0, + static_cast (serverAddress)->ai_addr, + (beast_socklen_t) static_cast (serverAddress)->ai_addrlen) + : -1; +} + +bool DatagramSocket::isLocal() const noexcept +{ + return hostName == "127.0.0.1"; +} + +#if BEAST_MSVC + #pragma warning (pop) +#endif diff --git a/modules/beast_core/network/beast_Socket.h b/modules/beast_core/network/beast_Socket.h new file mode 100644 index 0000000000..91db75fe83 --- /dev/null +++ b/modules/beast_core/network/beast_Socket.h @@ -0,0 +1,302 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SOCKET_BEASTHEADER +#define BEAST_SOCKET_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** + A wrapper for a streaming (TCP) socket. + + This allows low-level use of sockets; for an easier-to-use messaging layer on top of + sockets, you could also try the InterprocessConnection class. + + @see DatagramSocket, InterprocessConnection, InterprocessConnectionServer +*/ +class BEAST_API StreamingSocket +{ +public: + //============================================================================== + /** Creates an uninitialised socket. + + To connect it, use the connect() method, after which you can read() or write() + to it. + + To wait for other sockets to connect to this one, the createListener() method + enters "listener" mode, and can be used to spawn new sockets for each connection + that comes along. + */ + StreamingSocket(); + + /** Destructor. */ + ~StreamingSocket(); + + //============================================================================== + /** Binds the socket to the specified local port. + + @returns true on success; false may indicate that another socket is already bound + on the same port + */ + bool bindToPort (int localPortNumber); + + /** Tries to connect the socket to hostname:port. + + If timeOutMillisecs is 0, then this method will block until the operating system + rejects the connection (which could take a long time). + + @returns true if it succeeds. + @see isConnected + */ + bool connect (const String& remoteHostname, + int remotePortNumber, + int timeOutMillisecs = 3000); + + /** True if the socket is currently connected. */ + bool isConnected() const noexcept { return connected; } + + /** Closes the connection. */ + void close(); + + /** Returns the name of the currently connected host. */ + const String& getHostName() const noexcept { return hostName; } + + /** Returns the port number that's currently open. */ + int getPort() const noexcept { return portNumber; } + + /** True if the socket is connected to this machine rather than over the network. */ + bool isLocal() const noexcept; + + /** Returns the OS's socket handle that's currently open. */ + int getRawSocketHandle() const noexcept { return handle; } + + //============================================================================== + /** Waits until the socket is ready for reading or writing. + + If readyForReading is true, it will wait until the socket is ready for + reading; if false, it will wait until it's ready for writing. + + If the timeout is < 0, it will wait forever, or else will give up after + the specified time. + + If the socket is ready on return, this returns 1. If it times-out before + the socket becomes ready, it returns 0. If an error occurs, it returns -1. + */ + int waitUntilReady (bool readyForReading, + int timeoutMsecs) const; + + /** Reads bytes from the socket. + + If blockUntilSpecifiedAmountHasArrived is true, the method will block until + maxBytesToRead bytes have been read, (or until an error occurs). If this + flag is false, the method will return as much data as is currently available + without blocking. + + @returns the number of bytes read, or -1 if there was an error. + @see waitUntilReady + */ + int read (void* destBuffer, int maxBytesToRead, + bool blockUntilSpecifiedAmountHasArrived); + + /** Writes bytes to the socket from a buffer. + + Note that this method will block unless you have checked the socket is ready + for writing before calling it (see the waitUntilReady() method). + + @returns the number of bytes written, or -1 if there was an error. + */ + int write (const void* sourceBuffer, int numBytesToWrite); + + //============================================================================== + /** Puts this socket into "listener" mode. + + When in this mode, your thread can call waitForNextConnection() repeatedly, + which will spawn new sockets for each new connection, so that these can + be handled in parallel by other threads. + + @param portNumber the port number to listen on + @param localHostName the interface address to listen on - pass an empty + string to listen on all addresses + @returns true if it manages to open the socket successfully. + + @see waitForNextConnection + */ + bool createListener (int portNumber, const String& localHostName = String::empty); + + /** When in "listener" mode, this waits for a connection and spawns it as a new + socket. + + The object that gets returned will be owned by the caller. + + This method can only be called after using createListener(). + + @see createListener + */ + StreamingSocket* waitForNextConnection() const; + +private: + //============================================================================== + String hostName; + int volatile portNumber, handle; + bool connected, isListener; + + StreamingSocket (const String& hostname, int portNumber, int handle); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StreamingSocket) +}; + + +//============================================================================== +/** + A wrapper for a datagram (UDP) socket. + + This allows low-level use of sockets; for an easier-to-use messaging layer on top of + sockets, you could also try the InterprocessConnection class. + + @see StreamingSocket, InterprocessConnection, InterprocessConnectionServer +*/ +class BEAST_API DatagramSocket +{ +public: + //============================================================================== + /** + Creates an (uninitialised) datagram socket. + + The localPortNumber is the port on which to bind this socket. If this value is 0, + the port number is assigned by the operating system. + + To use the socket for sending, call the connect() method. This will not immediately + make a connection, but will save the destination you've provided. After this, you can + call read() or write(). + + If enableBroadcasting is true, the socket will be allowed to send broadcast messages + (may require extra privileges on linux) + + To wait for other sockets to connect to this one, call waitForNextConnection(). + */ + DatagramSocket (int localPortNumber, + bool enableBroadcasting = false); + + /** Destructor. */ + ~DatagramSocket(); + + //============================================================================== + /** Binds the socket to the specified local port. + + @returns true on success; false may indicate that another socket is already bound + on the same port + */ + bool bindToPort (int localPortNumber); + + /** Tries to connect the socket to hostname:port. + + If timeOutMillisecs is 0, then this method will block until the operating system + rejects the connection (which could take a long time). + + @returns true if it succeeds. + @see isConnected + */ + bool connect (const String& remoteHostname, + int remotePortNumber, + int timeOutMillisecs = 3000); + + /** True if the socket is currently connected. */ + bool isConnected() const noexcept { return connected; } + + /** Closes the connection. */ + void close(); + + /** Returns the name of the currently connected host. */ + const String& getHostName() const noexcept { return hostName; } + + /** Returns the port number that's currently open. */ + int getPort() const noexcept { return portNumber; } + + /** True if the socket is connected to this machine rather than over the network. */ + bool isLocal() const noexcept; + + /** Returns the OS's socket handle that's currently open. */ + int getRawSocketHandle() const noexcept { return handle; } + + //============================================================================== + /** Waits until the socket is ready for reading or writing. + + If readyForReading is true, it will wait until the socket is ready for + reading; if false, it will wait until it's ready for writing. + + If the timeout is < 0, it will wait forever, or else will give up after + the specified time. + + If the socket is ready on return, this returns 1. If it times-out before + the socket becomes ready, it returns 0. If an error occurs, it returns -1. + */ + int waitUntilReady (bool readyForReading, + int timeoutMsecs) const; + + /** Reads bytes from the socket. + + If blockUntilSpecifiedAmountHasArrived is true, the method will block until + maxBytesToRead bytes have been read, (or until an error occurs). If this + flag is false, the method will return as much data as is currently available + without blocking. + + @returns the number of bytes read, or -1 if there was an error. + @see waitUntilReady + */ + int read (void* destBuffer, int maxBytesToRead, + bool blockUntilSpecifiedAmountHasArrived); + + /** Writes bytes to the socket from a buffer. + + Note that this method will block unless you have checked the socket is ready + for writing before calling it (see the waitUntilReady() method). + + @returns the number of bytes written, or -1 if there was an error. + */ + int write (const void* sourceBuffer, int numBytesToWrite); + + //============================================================================== + /** This waits for incoming data to be sent, and returns a socket that can be used + to read it. + + The object that gets returned is owned by the caller, and can't be used for + sending, but can be used to read the data. + */ + DatagramSocket* waitForNextConnection() const; + +private: + //============================================================================== + String hostName; + int volatile portNumber, handle; + bool connected, allowBroadcast; + void* serverAddress; + + DatagramSocket (const String& hostname, int portNumber, int handle, int localPortNumber); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DatagramSocket) +}; + + +#endif // BEAST_SOCKET_BEASTHEADER diff --git a/modules/beast_core/network/beast_URL.cpp b/modules/beast_core/network/beast_URL.cpp new file mode 100644 index 0000000000..8e71c6db9e --- /dev/null +++ b/modules/beast_core/network/beast_URL.cpp @@ -0,0 +1,468 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +URL::URL() +{ +} + +URL::URL (const String& url_) + : url (url_) +{ + int i = url.indexOfChar ('?'); + + if (i >= 0) + { + do + { + const int nextAmp = url.indexOfChar (i + 1, '&'); + const int equalsPos = url.indexOfChar (i + 1, '='); + + if (equalsPos > i + 1) + { + if (nextAmp < 0) + { + addParameter (removeEscapeChars (url.substring (i + 1, equalsPos)), + removeEscapeChars (url.substring (equalsPos + 1))); + } + else if (nextAmp > 0 && equalsPos < nextAmp) + { + addParameter (removeEscapeChars (url.substring (i + 1, equalsPos)), + removeEscapeChars (url.substring (equalsPos + 1, nextAmp))); + } + } + + i = nextAmp; + } + while (i >= 0); + + url = url.upToFirstOccurrenceOf ("?", false, false); + } +} + +URL::URL (const URL& other) + : url (other.url), + postData (other.postData), + parameterNames (other.parameterNames), + parameterValues (other.parameterValues), + filesToUpload (other.filesToUpload), + mimeTypes (other.mimeTypes) +{ +} + +URL& URL::operator= (const URL& other) +{ + url = other.url; + postData = other.postData; + parameterNames = other.parameterNames; + parameterValues = other.parameterValues; + filesToUpload = other.filesToUpload; + mimeTypes = other.mimeTypes; + + return *this; +} + +bool URL::operator== (const URL& other) const +{ + return url == other.url + && postData == other.postData + && parameterNames == other.parameterNames + && parameterValues == other.parameterValues + && filesToUpload == other.filesToUpload + && mimeTypes == other.mimeTypes; +} + +bool URL::operator!= (const URL& other) const +{ + return ! operator== (other); +} + +URL::~URL() +{ +} + +namespace URLHelpers +{ + static String getMangledParameters (const URL& url) + { + bassert (url.getParameterNames().size() == url.getParameterValues().size()); + String p; + + for (int i = 0; i < url.getParameterNames().size(); ++i) + { + if (i > 0) + p << '&'; + + p << URL::addEscapeChars (url.getParameterNames()[i], true) + << '=' + << URL::addEscapeChars (url.getParameterValues()[i], true); + } + + return p; + } + + static int findEndOfScheme (const String& url) + { + int i = 0; + + while (CharacterFunctions::isLetterOrDigit (url[i]) + || url[i] == '+' || url[i] == '-' || url[i] == '.') + ++i; + + return url[i] == ':' ? i + 1 : 0; + } + + static int findStartOfNetLocation (const String& url) + { + int start = findEndOfScheme (url); + while (url[start] == '/') + ++start; + + return start; + } + + static int findStartOfPath (const String& url) + { + return url.indexOfChar (findStartOfNetLocation (url), '/') + 1; + } + + static void createHeadersAndPostData (const URL& url, String& headers, MemoryBlock& postData) + { + MemoryOutputStream data (postData, false); + + if (url.getFilesToUpload().size() > 0) + { + // need to upload some files, so do it as multi-part... + const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); + + headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; + + data << "--" << boundary; + + for (int i = 0; i < url.getParameterNames().size(); ++i) + { + data << "\r\nContent-Disposition: form-data; name=\"" + << url.getParameterNames() [i] + << "\"\r\n\r\n" + << url.getParameterValues() [i] + << "\r\n--" + << boundary; + } + + for (int i = 0; i < url.getFilesToUpload().size(); ++i) + { + const File file (url.getFilesToUpload().getAllValues() [i]); + const String paramName (url.getFilesToUpload().getAllKeys() [i]); + + data << "\r\nContent-Disposition: form-data; name=\"" << paramName + << "\"; filename=\"" << file.getFileName() << "\"\r\n"; + + const String mimeType (url.getMimeTypesOfUploadFiles() + .getValue (paramName, String::empty)); + + if (mimeType.isNotEmpty()) + data << "Content-Type: " << mimeType << "\r\n"; + + data << "Content-Transfer-Encoding: binary\r\n\r\n" + << file << "\r\n--" << boundary; + } + + data << "--\r\n"; + } + else + { + data << getMangledParameters (url) + << url.getPostData(); + + // just a short text attachment, so use simple url encoding.. + headers << "Content-Type: application/x-www-form-urlencoded\r\nContent-length: " + << (int) data.getDataSize() << "\r\n"; + } + } + + static void concatenatePaths (String& path, const String& suffix) + { + if (! path.endsWithChar ('/')) + path << '/'; + + if (suffix.startsWithChar ('/')) + path += suffix.substring (1); + else + path += suffix; + } +} + +void URL::addParameter (const String& name, const String& value) +{ + parameterNames.add (name); + parameterValues.add (value); +} + +String URL::toString (const bool includeGetParameters) const +{ + if (includeGetParameters && parameterNames.size() > 0) + return url + "?" + URLHelpers::getMangledParameters (*this); + + return url; +} + +bool URL::isWellFormed() const +{ + //xxx TODO + return url.isNotEmpty(); +} + +String URL::getDomain() const +{ + const int start = URLHelpers::findStartOfNetLocation (url); + const int end1 = url.indexOfChar (start, '/'); + const int end2 = url.indexOfChar (start, ':'); + + const int end = (end1 < 0 && end2 < 0) ? std::numeric_limits::max() + : ((end1 < 0 || end2 < 0) ? bmax (end1, end2) + : bmin (end1, end2)); + return url.substring (start, end); +} + +String URL::getSubPath() const +{ + const int startOfPath = URLHelpers::findStartOfPath (url); + + return startOfPath <= 0 ? String::empty + : url.substring (startOfPath); +} + +String URL::getScheme() const +{ + return url.substring (0, URLHelpers::findEndOfScheme (url) - 1); +} + +int URL::getPort() const +{ + const int colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':'); + + return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0; +} + +URL URL::withNewSubPath (const String& newPath) const +{ + const int startOfPath = URLHelpers::findStartOfPath (url); + + URL u (*this); + + if (startOfPath > 0) + u.url = url.substring (0, startOfPath); + + URLHelpers::concatenatePaths (u.url, newPath); + return u; +} + +URL URL::getChildURL (const String& subPath) const +{ + URL u (*this); + URLHelpers::concatenatePaths (u.url, subPath); + return u; +} + +//============================================================================== +bool URL::isProbablyAWebsiteURL (const String& possibleURL) +{ + const char* validProtocols[] = { "http:", "ftp:", "https:" }; + + for (int i = 0; i < numElementsInArray (validProtocols); ++i) + if (possibleURL.startsWithIgnoreCase (validProtocols[i])) + return true; + + if (possibleURL.containsChar ('@') + || possibleURL.containsChar (' ')) + return false; + + const String topLevelDomain (possibleURL.upToFirstOccurrenceOf ("/", false, false) + .fromLastOccurrenceOf (".", false, false)); + + return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3; +} + +bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress) +{ + const int atSign = possibleEmailAddress.indexOfChar ('@'); + + return atSign > 0 + && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1) + && (! possibleEmailAddress.endsWithChar ('.')); +} + +//============================================================================== +InputStream* URL::createInputStream (const bool usePostCommand, + OpenStreamProgressCallback* const progressCallback, + void* const progressCallbackContext, + const String& extraHeaders, + const int timeOutMs, + StringPairArray* const responseHeaders) const +{ + String headers; + MemoryBlock headersAndPostData; + + if (usePostCommand) + URLHelpers::createHeadersAndPostData (*this, headers, headersAndPostData); + + headers += extraHeaders; + + if (! headers.endsWithChar ('\n')) + headers << "\r\n"; + + return createNativeStream (toString (! usePostCommand), usePostCommand, headersAndPostData, + progressCallback, progressCallbackContext, + headers, timeOutMs, responseHeaders); +} + +//============================================================================== +bool URL::readEntireBinaryStream (MemoryBlock& destData, + const bool usePostCommand) const +{ + const ScopedPointer in (createInputStream (usePostCommand)); + + if (in != nullptr) + { + in->readIntoMemoryBlock (destData); + return true; + } + + return false; +} + +String URL::readEntireTextStream (const bool usePostCommand) const +{ + const ScopedPointer in (createInputStream (usePostCommand)); + + if (in != nullptr) + return in->readEntireStreamAsString(); + + return String::empty; +} + +XmlElement* URL::readEntireXmlStream (const bool usePostCommand) const +{ + return XmlDocument::parse (readEntireTextStream (usePostCommand)); +} + +//============================================================================== +URL URL::withParameter (const String& parameterName, + const String& parameterValue) const +{ + URL u (*this); + u.addParameter (parameterName, parameterValue); + return u; +} + +URL URL::withFileToUpload (const String& parameterName, + const File& fileToUpload, + const String& mimeType) const +{ + bassert (mimeType.isNotEmpty()); // You need to supply a mime type! + + URL u (*this); + u.filesToUpload.set (parameterName, fileToUpload.getFullPathName()); + u.mimeTypes.set (parameterName, mimeType); + return u; +} + +URL URL::withPOSTData (const String& postData_) const +{ + URL u (*this); + u.postData = postData_; + return u; +} + +const StringPairArray& URL::getFilesToUpload() const +{ + return filesToUpload; +} + +const StringPairArray& URL::getMimeTypesOfUploadFiles() const +{ + return mimeTypes; +} + +//============================================================================== +String URL::removeEscapeChars (const String& s) +{ + String result (s.replaceCharacter ('+', ' ')); + + if (! result.containsChar ('%')) + return result; + + // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode + // after all the replacements have been made, so that multi-byte chars are handled. + Array utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8()); + + for (int i = 0; i < utf8.size(); ++i) + { + if (utf8.getUnchecked(i) == '%') + { + const int hexDigit1 = CharacterFunctions::getHexDigitValue ((beast_wchar) (uint8) utf8 [i + 1]); + const int hexDigit2 = CharacterFunctions::getHexDigitValue ((beast_wchar) (uint8) utf8 [i + 2]); + + if (hexDigit1 >= 0 && hexDigit2 >= 0) + { + utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2)); + utf8.removeRange (i + 1, 2); + } + } + } + + return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); +} + +String URL::addEscapeChars (const String& s, const bool isParameter) +{ + const CharPointer_UTF8 legalChars (isParameter ? "_-.*!'()" + : ",$_-.*!'()"); + + Array utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8()); + + for (int i = 0; i < utf8.size(); ++i) + { + const char c = utf8.getUnchecked(i); + + if (! (CharacterFunctions::isLetterOrDigit (c) + || legalChars.indexOf ((beast_wchar) c) >= 0)) + { + utf8.set (i, '%'); + utf8.insert (++i, "0123456789abcdef" [((uint8) c) >> 4]); + utf8.insert (++i, "0123456789abcdef" [c & 15]); + } + } + + return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); +} + +//============================================================================== +bool URL::launchInDefaultBrowser() const +{ + String u (toString (true)); + + if (u.containsChar ('@') && ! u.containsChar (':')) + u = "mailto:" + u; + + return Process::openDocument (u, String::empty); +} diff --git a/modules/beast_core/network/beast_URL.h b/modules/beast_core/network/beast_URL.h new file mode 100644 index 0000000000..dd6963341f --- /dev/null +++ b/modules/beast_core/network/beast_URL.h @@ -0,0 +1,345 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_URL_BEASTHEADER +#define BEAST_URL_BEASTHEADER + +#include "../text/beast_StringPairArray.h" +#include "../files/beast_File.h" +class InputStream; +class XmlElement; + + +//============================================================================== +/** + Represents a URL and has a bunch of useful functions to manipulate it. + + This class can be used to launch URLs in browsers, and also to create + InputStreams that can read from remote http or ftp sources. +*/ +class BEAST_API URL +{ +public: + //============================================================================== + /** Creates an empty URL. */ + URL(); + + /** Creates a URL from a string. */ + URL (const String& url); + + /** Creates a copy of another URL. */ + URL (const URL& other); + + /** Destructor. */ + ~URL(); + + /** Copies this URL from another one. */ + URL& operator= (const URL& other); + + /** Compares two URLs. + All aspects of the URLs must be identical for them to match, including any parameters, + upload files, etc. + */ + bool operator== (const URL&) const; + bool operator!= (const URL&) const; + + //============================================================================== + /** Returns a string version of the URL. + + If includeGetParameters is true and any parameters have been set with the + withParameter() method, then the string will have these appended on the + end and url-encoded. + */ + String toString (bool includeGetParameters) const; + + /** True if it seems to be valid. */ + bool isWellFormed() const; + + /** Returns just the domain part of the URL. + + E.g. for "http://www.xyz.com/foobar", this will return "www.xyz.com". + */ + String getDomain() const; + + /** Returns the path part of the URL. + + E.g. for "http://www.xyz.com/foo/bar?x=1", this will return "foo/bar". + */ + String getSubPath() const; + + /** Returns the scheme of the URL. + + E.g. for "http://www.xyz.com/foobar", this will return "http". (It won't + include the colon). + */ + String getScheme() const; + + /** Attempts to read a port number from the URL. + @returns the port number, or 0 if none is explicitly specified. + */ + int getPort() const; + + /** Returns a new version of this URL that uses a different sub-path. + + E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with + "bar", it'll return "http://www.xyz.com/bar?x=1". + */ + URL withNewSubPath (const String& newPath) const; + + /** Returns a new URL that refers to a sub-path relative to this one. + + E.g. if the URL is "http://www.xyz.com/foo" and you call this with + "bar", it'll return "http://www.xyz.com/foo/bar". Note that there's no way for + this method to know whether the original URL is a file or directory, so it's + up to you to make sure it's a directory. It also won't attempt to be smart about + the content of the childPath string, so if this string is an absolute URL, it'll + still just get bolted onto the end of the path. + + @see File::getChildFile + */ + URL getChildURL (const String& subPath) const; + + //============================================================================== + /** Returns a copy of this URL, with a GET or POST parameter added to the end. + + Any control characters in the value will be encoded. + + e.g. calling "withParameter ("amount", "some fish") for the url "www.fish.com" + would produce a new url whose toString(true) method would return + "www.fish.com?amount=some+fish". + + @see getParameterNames, getParameterValues + */ + URL withParameter (const String& parameterName, + const String& parameterValue) const; + + /** Returns a copy of this URl, with a file-upload type parameter added to it. + + When performing a POST where one of your parameters is a binary file, this + lets you specify the file. + + Note that the filename is stored, but the file itself won't actually be read + until this URL is later used to create a network input stream. + */ + URL withFileToUpload (const String& parameterName, + const File& fileToUpload, + const String& mimeType) const; + + /** Returns an array of the names of all the URL's parameters. + + E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would + contain two items: "type" and "amount". + + You can call getParameterValues() to get the corresponding value of each + parameter. Note that the list can contain multiple parameters with the same name. + + @see getParameterValues, withParameter + */ + const StringArray& getParameterNames() const noexcept { return parameterNames; } + + /** Returns an array of the values of all the URL's parameters. + + E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would + contain two items: "haddock" and "some fish". + + The values returned will have been cleaned up to remove any escape characters. + + You can call getParameterNames() to get the corresponding name of each + parameter. Note that the list can contain multiple parameters with the same name. + + @see getParameterNames, withParameter + */ + const StringArray& getParameterValues() const noexcept { return parameterValues; } + + /** Returns the set of files that should be uploaded as part of a POST operation. + + This is the set of files that were added to the URL with the withFileToUpload() + method. + */ + const StringPairArray& getFilesToUpload() const; + + /** Returns the set of mime types associated with each of the upload files. + */ + const StringPairArray& getMimeTypesOfUploadFiles() const; + + /** Returns a copy of this URL, with a block of data to send as the POST data. + + If you're setting the POST data, be careful not to have any parameters set + as well, otherwise it'll all get thrown in together, and might not have the + desired effect. + + If the URL already contains some POST data, this will replace it, rather + than being appended to it. + + This data will only be used if you specify a post operation when you call + createInputStream(). + */ + URL withPOSTData (const String& postData) const; + + /** Returns the data that was set using withPOSTData(). */ + const String& getPostData() const noexcept { return postData; } + + //============================================================================== + /** Tries to launch the system's default browser to open the URL. + + Returns true if this seems to have worked. + */ + bool launchInDefaultBrowser() const; + + //============================================================================== + /** Takes a guess as to whether a string might be a valid website address. + + This isn't foolproof! + */ + static bool isProbablyAWebsiteURL (const String& possibleURL); + + /** Takes a guess as to whether a string might be a valid email address. + + This isn't foolproof! + */ + static bool isProbablyAnEmailAddress (const String& possibleEmailAddress); + + //============================================================================== + /** This callback function can be used by the createInputStream() method. + + It allows your app to receive progress updates during a lengthy POST operation. If you + want to continue the operation, this should return true, or false to abort. + */ + typedef bool (OpenStreamProgressCallback) (void* context, int bytesSent, int totalBytes); + + /** Attempts to open a stream that can read from this URL. + + @param usePostCommand if true, it will try to do use a http 'POST' to pass + the paramters, otherwise it'll encode them into the + URL and do a 'GET'. + @param progressCallback if this is non-zero, it lets you supply a callback function + to keep track of the operation's progress. This can be useful + for lengthy POST operations, so that you can provide user feedback. + @param progressCallbackContext if a callback is specified, this value will be passed to + the function + @param extraHeaders if not empty, this string is appended onto the headers that + are used for the request. It must therefore be a valid set of HTML + header directives, separated by newlines. + @param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If + a negative number, it will be infinite. Otherwise it specifies a + time in milliseconds. + @param responseHeaders if this is non-zero, all the (key, value) pairs received as headers + in the response will be stored in this array + @returns an input stream that the caller must delete, or a null pointer if there was an + error trying to open it. + */ + InputStream* createInputStream (bool usePostCommand, + OpenStreamProgressCallback* progressCallback = nullptr, + void* progressCallbackContext = nullptr, + const String& extraHeaders = String::empty, + int connectionTimeOutMs = 0, + StringPairArray* responseHeaders = nullptr) const; + + + //============================================================================== + /** Tries to download the entire contents of this URL into a binary data block. + + If it succeeds, this will return true and append the data it read onto the end + of the memory block. + + @param destData the memory block to append the new data to + @param usePostCommand whether to use a POST command to get the data (uses + a GET command if this is false) + @see readEntireTextStream, readEntireXmlStream + */ + bool readEntireBinaryStream (MemoryBlock& destData, + bool usePostCommand = false) const; + + /** Tries to download the entire contents of this URL as a string. + + If it fails, this will return an empty string, otherwise it will return the + contents of the downloaded file. If you need to distinguish between a read + operation that fails and one that returns an empty string, you'll need to use + a different method, such as readEntireBinaryStream(). + + @param usePostCommand whether to use a POST command to get the data (uses + a GET command if this is false) + @see readEntireBinaryStream, readEntireXmlStream + */ + String readEntireTextStream (bool usePostCommand = false) const; + + /** Tries to download the entire contents of this URL and parse it as XML. + + If it fails, or if the text that it reads can't be parsed as XML, this will + return 0. + + When it returns a valid XmlElement object, the caller is responsibile for deleting + this object when no longer needed. + + @param usePostCommand whether to use a POST command to get the data (uses + a GET command if this is false) + + @see readEntireBinaryStream, readEntireTextStream + */ + XmlElement* readEntireXmlStream (bool usePostCommand = false) const; + + //============================================================================== + /** Adds escape sequences to a string to encode any characters that aren't + legal in a URL. + + E.g. any spaces will be replaced with "%20". + + This is the opposite of removeEscapeChars(). + + If isParameter is true, it means that the string is going to be used + as a parameter, so it also encodes '$' and ',' (which would otherwise + be legal in a URL. + + @see removeEscapeChars + */ + static String addEscapeChars (const String& stringToAddEscapeCharsTo, + bool isParameter); + + /** Replaces any escape character sequences in a string with their original + character codes. + + E.g. any instances of "%20" will be replaced by a space. + + This is the opposite of addEscapeChars(). + + @see addEscapeChars + */ + static String removeEscapeChars (const String& stringToRemoveEscapeCharsFrom); + +private: + //============================================================================== + String url, postData; + StringArray parameterNames, parameterValues; + StringPairArray filesToUpload, mimeTypes; + + void addParameter (const String&, const String&); + + static InputStream* createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, + OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext, const String& headers, + const int timeOutMs, StringPairArray* responseHeaders); + BEAST_LEAK_DETECTOR (URL) +}; + + +#endif // BEAST_URL_BEASTHEADER diff --git a/modules/beast_core/streams/beast_BufferedInputStream.cpp b/modules/beast_core/streams/beast_BufferedInputStream.cpp new file mode 100644 index 0000000000..8fcb738681 --- /dev/null +++ b/modules/beast_core/streams/beast_BufferedInputStream.cpp @@ -0,0 +1,193 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +namespace +{ + int calcBufferStreamBufferSize (int requestedSize, InputStream* const source) noexcept + { + // You need to supply a real stream when creating a BufferedInputStream + bassert (source != nullptr); + + requestedSize = bmax (256, requestedSize); + + const int64 sourceSize = source->getTotalLength(); + if (sourceSize >= 0 && sourceSize < requestedSize) + requestedSize = bmax (32, (int) sourceSize); + + return requestedSize; + } +} + +//============================================================================== +BufferedInputStream::BufferedInputStream (InputStream* const sourceStream, const int bufferSize_, + const bool deleteSourceWhenDestroyed) + : source (sourceStream, deleteSourceWhenDestroyed), + bufferSize (calcBufferStreamBufferSize (bufferSize_, sourceStream)), + position (sourceStream->getPosition()), + lastReadPos (0), + bufferStart (position), + bufferOverlap (128) +{ + buffer.malloc ((size_t) bufferSize); +} + +BufferedInputStream::BufferedInputStream (InputStream& sourceStream, const int bufferSize_) + : source (&sourceStream, false), + bufferSize (calcBufferStreamBufferSize (bufferSize_, &sourceStream)), + position (sourceStream.getPosition()), + lastReadPos (0), + bufferStart (position), + bufferOverlap (128) +{ + buffer.malloc ((size_t) bufferSize); +} + +BufferedInputStream::~BufferedInputStream() +{ +} + +//============================================================================== +int64 BufferedInputStream::getTotalLength() +{ + return source->getTotalLength(); +} + +int64 BufferedInputStream::getPosition() +{ + return position; +} + +bool BufferedInputStream::setPosition (int64 newPosition) +{ + position = bmax ((int64) 0, newPosition); + return true; +} + +bool BufferedInputStream::isExhausted() +{ + return position >= lastReadPos && source->isExhausted(); +} + +void BufferedInputStream::ensureBuffered() +{ + const int64 bufferEndOverlap = lastReadPos - bufferOverlap; + + if (position < bufferStart || position >= bufferEndOverlap) + { + int bytesRead; + + if (position < lastReadPos + && position >= bufferEndOverlap + && position >= bufferStart) + { + const int bytesToKeep = (int) (lastReadPos - position); + memmove (buffer, buffer + (int) (position - bufferStart), (size_t) bytesToKeep); + + bufferStart = position; + + bytesRead = source->read (buffer + bytesToKeep, + (int) (bufferSize - bytesToKeep)); + + lastReadPos += bytesRead; + bytesRead += bytesToKeep; + } + else + { + bufferStart = position; + source->setPosition (bufferStart); + bytesRead = source->read (buffer, bufferSize); + lastReadPos = bufferStart + bytesRead; + } + + while (bytesRead < bufferSize) + buffer [bytesRead++] = 0; + } +} + +int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) +{ + bassert (destBuffer != nullptr && maxBytesToRead >= 0); + + if (position >= bufferStart + && position + maxBytesToRead <= lastReadPos) + { + memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) maxBytesToRead); + position += maxBytesToRead; + + return maxBytesToRead; + } + else + { + if (position < bufferStart || position >= lastReadPos) + ensureBuffered(); + + int bytesRead = 0; + + while (maxBytesToRead > 0) + { + const int bytesAvailable = bmin (maxBytesToRead, (int) (lastReadPos - position)); + + if (bytesAvailable > 0) + { + memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) bytesAvailable); + maxBytesToRead -= bytesAvailable; + bytesRead += bytesAvailable; + position += bytesAvailable; + destBuffer = static_cast (destBuffer) + bytesAvailable; + } + + const int64 oldLastReadPos = lastReadPos; + ensureBuffered(); + + if (oldLastReadPos == lastReadPos) + break; // if ensureBuffered() failed to read any more data, bail out + + if (isExhausted()) + break; + } + + return bytesRead; + } +} + +String BufferedInputStream::readString() +{ + if (position >= bufferStart + && position < lastReadPos) + { + const int maxChars = (int) (lastReadPos - position); + + const char* const src = buffer + (int) (position - bufferStart); + + for (int i = 0; i < maxChars; ++i) + { + if (src[i] == 0) + { + position += i + 1; + return String::fromUTF8 (src, i); + } + } + } + + return InputStream::readString(); +} diff --git a/modules/beast_core/streams/beast_BufferedInputStream.h b/modules/beast_core/streams/beast_BufferedInputStream.h new file mode 100644 index 0000000000..54ef9668e1 --- /dev/null +++ b/modules/beast_core/streams/beast_BufferedInputStream.h @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_BUFFEREDINPUTSTREAM_BEASTHEADER +#define BEAST_BUFFEREDINPUTSTREAM_BEASTHEADER + +#include "beast_InputStream.h" +#include "../memory/beast_OptionalScopedPointer.h" +#include "../memory/beast_HeapBlock.h" + + +//============================================================================== +/** Wraps another input stream, and reads from it using an intermediate buffer + + If you're using an input stream such as a file input stream, and making lots of + small read accesses to it, it's probably sensible to wrap it in one of these, + so that the source stream gets accessed in larger chunk sizes, meaning less + work for the underlying stream. +*/ +class BEAST_API BufferedInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a BufferedInputStream from an input source. + + @param sourceStream the source stream to read from + @param bufferSize the size of reservoir to use to buffer the source + @param deleteSourceWhenDestroyed whether the sourceStream that is passed in should be + deleted by this object when it is itself deleted. + */ + BufferedInputStream (InputStream* sourceStream, + int bufferSize, + bool deleteSourceWhenDestroyed); + + /** Creates a BufferedInputStream from an input source. + + @param sourceStream the source stream to read from - the source stream must not + be deleted until this object has been destroyed. + @param bufferSize the size of reservoir to use to buffer the source + */ + BufferedInputStream (InputStream& sourceStream, int bufferSize); + + /** Destructor. + + This may also delete the source stream, if that option was chosen when the + buffered stream was created. + */ + ~BufferedInputStream(); + + + //============================================================================== + int64 getTotalLength(); + int64 getPosition(); + bool setPosition (int64 newPosition); + int read (void* destBuffer, int maxBytesToRead); + String readString(); + bool isExhausted(); + + +private: + //============================================================================== + OptionalScopedPointer source; + int bufferSize; + int64 position, lastReadPos, bufferStart, bufferOverlap; + HeapBlock buffer; + void ensureBuffered(); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferedInputStream) +}; + +#endif // BEAST_BUFFEREDINPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/streams/beast_FileInputSource.cpp b/modules/beast_core/streams/beast_FileInputSource.cpp new file mode 100644 index 0000000000..a2f677dcd5 --- /dev/null +++ b/modules/beast_core/streams/beast_FileInputSource.cpp @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +FileInputSource::FileInputSource (const File& f, bool useFileTimeInHash) + : file (f), useFileTimeInHashGeneration (useFileTimeInHash) +{ +} + +FileInputSource::~FileInputSource() +{ +} + +InputStream* FileInputSource::createInputStream() +{ + return file.createInputStream(); +} + +InputStream* FileInputSource::createInputStreamFor (const String& relatedItemPath) +{ + return file.getSiblingFile (relatedItemPath).createInputStream(); +} + +int64 FileInputSource::hashCode() const +{ + int64 h = file.hashCode(); + + if (useFileTimeInHashGeneration) + h ^= file.getLastModificationTime().toMilliseconds(); + + return h; +} diff --git a/modules/beast_core/streams/beast_FileInputSource.h b/modules/beast_core/streams/beast_FileInputSource.h new file mode 100644 index 0000000000..8726144f42 --- /dev/null +++ b/modules/beast_core/streams/beast_FileInputSource.h @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_FILEINPUTSOURCE_BEASTHEADER +#define BEAST_FILEINPUTSOURCE_BEASTHEADER + +#include "beast_InputSource.h" +#include "../files/beast_File.h" + + +//============================================================================== +/** + A type of InputSource that represents a normal file. + + @see InputSource +*/ +class BEAST_API FileInputSource : public InputSource +{ +public: + //============================================================================== + /** Creates a FileInputSource for a file. + If the useFileTimeInHashGeneration parameter is true, then this object's + hashCode() method will incorporate the file time into its hash code; if + false, only the file name will be used for the hash. + */ + FileInputSource (const File& file, bool useFileTimeInHashGeneration = false); + + /** Destructor. */ + ~FileInputSource(); + + InputStream* createInputStream(); + InputStream* createInputStreamFor (const String& relatedItemPath); + int64 hashCode() const; + +private: + //============================================================================== + const File file; + bool useFileTimeInHashGeneration; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputSource) +}; + + +#endif // BEAST_FILEINPUTSOURCE_BEASTHEADER diff --git a/modules/beast_core/streams/beast_InputSource.h b/modules/beast_core/streams/beast_InputSource.h new file mode 100644 index 0000000000..cd47a678c1 --- /dev/null +++ b/modules/beast_core/streams/beast_InputSource.h @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_INPUTSOURCE_BEASTHEADER +#define BEAST_INPUTSOURCE_BEASTHEADER + +#include "beast_InputStream.h" + +//============================================================================== +/** + A lightweight object that can create a stream to read some kind of resource. + + This may be used to refer to a file, or some other kind of source, allowing a + caller to create an input stream that can read from it when required. + + @see FileInputSource +*/ +class BEAST_API InputSource +{ +public: + //============================================================================== + InputSource() noexcept {} + + /** Destructor. */ + virtual ~InputSource() {} + + //============================================================================== + /** Returns a new InputStream to read this item. + + @returns an inputstream that the caller will delete, or nullptr if + the filename isn't found. + */ + virtual InputStream* createInputStream() = 0; + + /** Returns a new InputStream to read an item, relative. + + @param relatedItemPath the relative pathname of the resource that is required + @returns an inputstream that the caller will delete, or nullptr if + the item isn't found. + */ + virtual InputStream* createInputStreamFor (const String& relatedItemPath) = 0; + + /** Returns a hash code that uniquely represents this item. + */ + virtual int64 hashCode() const = 0; + + +private: + //============================================================================== + BEAST_LEAK_DETECTOR (InputSource) +}; + + +#endif // BEAST_INPUTSOURCE_BEASTHEADER diff --git a/modules/beast_core/streams/beast_InputStream.cpp b/modules/beast_core/streams/beast_InputStream.cpp new file mode 100644 index 0000000000..ee2adb6a8e --- /dev/null +++ b/modules/beast_core/streams/beast_InputStream.cpp @@ -0,0 +1,231 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +int64 InputStream::getNumBytesRemaining() +{ + int64 len = getTotalLength(); + + if (len >= 0) + len -= getPosition(); + + return len; +} + +char InputStream::readByte() +{ + char temp = 0; + read (&temp, 1); + return temp; +} + +bool InputStream::readBool() +{ + return readByte() != 0; +} + +short InputStream::readShort() +{ + char temp[2]; + + if (read (temp, 2) == 2) + return (short) ByteOrder::littleEndianShort (temp); + + return 0; +} + +short InputStream::readShortBigEndian() +{ + char temp[2]; + + if (read (temp, 2) == 2) + return (short) ByteOrder::bigEndianShort (temp); + + return 0; +} + +int InputStream::readInt() +{ + char temp[4]; + + if (read (temp, 4) == 4) + return (int) ByteOrder::littleEndianInt (temp); + + return 0; +} + +int InputStream::readIntBigEndian() +{ + char temp[4]; + + if (read (temp, 4) == 4) + return (int) ByteOrder::bigEndianInt (temp); + + return 0; +} + +int InputStream::readCompressedInt() +{ + const uint8 sizeByte = (uint8) readByte(); + if (sizeByte == 0) + return 0; + + const int numBytes = (sizeByte & 0x7f); + if (numBytes > 4) + { + jassertfalse; // trying to read corrupt data - this method must only be used + // to read data that was written by OutputStream::writeCompressedInt() + return 0; + } + + char bytes[4] = { 0, 0, 0, 0 }; + if (read (bytes, numBytes) != numBytes) + return 0; + + const int num = (int) ByteOrder::littleEndianInt (bytes); + return (sizeByte >> 7) ? -num : num; +} + +int64 InputStream::readInt64() +{ + union { uint8 asBytes[8]; uint64 asInt64; } n; + + if (read (n.asBytes, 8) == 8) + return (int64) ByteOrder::swapIfBigEndian (n.asInt64); + + return 0; +} + +int64 InputStream::readInt64BigEndian() +{ + union { uint8 asBytes[8]; uint64 asInt64; } n; + + if (read (n.asBytes, 8) == 8) + return (int64) ByteOrder::swapIfLittleEndian (n.asInt64); + + return 0; +} + +float InputStream::readFloat() +{ + // the union below relies on these types being the same size... + static_bassert (sizeof (int32) == sizeof (float)); + union { int32 asInt; float asFloat; } n; + n.asInt = (int32) readInt(); + return n.asFloat; +} + +float InputStream::readFloatBigEndian() +{ + union { int32 asInt; float asFloat; } n; + n.asInt = (int32) readIntBigEndian(); + return n.asFloat; +} + +double InputStream::readDouble() +{ + union { int64 asInt; double asDouble; } n; + n.asInt = readInt64(); + return n.asDouble; +} + +double InputStream::readDoubleBigEndian() +{ + union { int64 asInt; double asDouble; } n; + n.asInt = readInt64BigEndian(); + return n.asDouble; +} + +String InputStream::readString() +{ + MemoryBlock buffer (256); + char* data = static_cast (buffer.getData()); + size_t i = 0; + + while ((data[i] = readByte()) != 0) + { + if (++i >= buffer.getSize()) + { + buffer.setSize (buffer.getSize() + 512); + data = static_cast (buffer.getData()); + } + } + + return String::fromUTF8 (data, (int) i); +} + +String InputStream::readNextLine() +{ + MemoryBlock buffer (256); + char* data = static_cast (buffer.getData()); + size_t i = 0; + + while ((data[i] = readByte()) != 0) + { + if (data[i] == '\n') + break; + + if (data[i] == '\r') + { + const int64 lastPos = getPosition(); + + if (readByte() != '\n') + setPosition (lastPos); + + break; + } + + if (++i >= buffer.getSize()) + { + buffer.setSize (buffer.getSize() + 512); + data = static_cast (buffer.getData()); + } + } + + return String::fromUTF8 (data, (int) i); +} + +int InputStream::readIntoMemoryBlock (MemoryBlock& block, ssize_t numBytes) +{ + MemoryOutputStream mo (block, true); + return mo.writeFromInputStream (*this, numBytes); +} + +String InputStream::readEntireStreamAsString() +{ + MemoryOutputStream mo; + mo << *this; + return mo.toString(); +} + +//============================================================================== +void InputStream::skipNextBytes (int64 numBytesToSkip) +{ + if (numBytesToSkip > 0) + { + const int skipBufferSize = (int) bmin (numBytesToSkip, (int64) 16384); + HeapBlock temp ((size_t) skipBufferSize); + + while (numBytesToSkip > 0 && ! isExhausted()) + numBytesToSkip -= read (temp, (int) bmin (numBytesToSkip, (int64) skipBufferSize)); + } +} diff --git a/modules/beast_core/streams/beast_InputStream.h b/modules/beast_core/streams/beast_InputStream.h new file mode 100644 index 0000000000..d90bb5c599 --- /dev/null +++ b/modules/beast_core/streams/beast_InputStream.h @@ -0,0 +1,293 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_INPUTSTREAM_BEASTHEADER +#define BEAST_INPUTSTREAM_BEASTHEADER + +#include "../text/beast_String.h" +class MemoryBlock; + + +//============================================================================== +/** The base class for streams that read data. + + Input and output streams are used throughout the library - subclasses can override + some or all of the virtual functions to implement their behaviour. + + @see OutputStream, MemoryInputStream, BufferedInputStream, FileInputStream +*/ +class BEAST_API InputStream +{ +public: + /** Destructor. */ + virtual ~InputStream() {} + + //============================================================================== + /** Returns the total number of bytes available for reading in this stream. + + Note that this is the number of bytes available from the start of the + stream, not from the current position. + + If the size of the stream isn't actually known, this will return -1. + + @see getNumBytesRemaining + */ + virtual int64 getTotalLength() = 0; + + /** Returns the number of bytes available for reading, or a negative value if + the remaining length is not known. + @see getTotalLength + */ + int64 getNumBytesRemaining(); + + /** Returns true if the stream has no more data to read. */ + virtual bool isExhausted() = 0; + + //============================================================================== + /** Reads some data from the stream into a memory buffer. + + This is the only read method that subclasses actually need to implement, as the + InputStream base class implements the other read methods in terms of this one (although + it's often more efficient for subclasses to implement them directly). + + @param destBuffer the destination buffer for the data. This must not be null. + @param maxBytesToRead the maximum number of bytes to read - make sure the + memory block passed in is big enough to contain this + many bytes. This value must not be negative. + + @returns the actual number of bytes that were read, which may be less than + maxBytesToRead if the stream is exhausted before it gets that far + */ + virtual int read (void* destBuffer, int maxBytesToRead) = 0; + + /** Reads a byte from the stream. + + If the stream is exhausted, this will return zero. + + @see OutputStream::writeByte + */ + virtual char readByte(); + + /** Reads a boolean from the stream. + + The bool is encoded as a single byte - 1 for true, 0 for false. + + If the stream is exhausted, this will return false. + + @see OutputStream::writeBool + */ + virtual bool readBool(); + + /** Reads two bytes from the stream as a little-endian 16-bit value. + + If the next two bytes read are byte1 and byte2, this returns + (byte1 | (byte2 << 8)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeShort, readShortBigEndian + */ + virtual short readShort(); + + /** Reads two bytes from the stream as a little-endian 16-bit value. + + If the next two bytes read are byte1 and byte2, this returns + (byte2 | (byte1 << 8)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeShortBigEndian, readShort + */ + virtual short readShortBigEndian(); + + /** Reads four bytes from the stream as a little-endian 32-bit value. + + If the next four bytes are byte1 to byte4, this returns + (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt, readIntBigEndian + */ + virtual int readInt(); + + /** Reads four bytes from the stream as a big-endian 32-bit value. + + If the next four bytes are byte1 to byte4, this returns + (byte4 | (byte3 << 8) | (byte2 << 16) | (byte1 << 24)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeIntBigEndian, readInt + */ + virtual int readIntBigEndian(); + + /** Reads eight bytes from the stream as a little-endian 64-bit value. + + If the next eight bytes are byte1 to byte8, this returns + (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24) | (byte5 << 32) | (byte6 << 40) | (byte7 << 48) | (byte8 << 56)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt64, readInt64BigEndian + */ + virtual int64 readInt64(); + + /** Reads eight bytes from the stream as a big-endian 64-bit value. + + If the next eight bytes are byte1 to byte8, this returns + (byte8 | (byte7 << 8) | (byte6 << 16) | (byte5 << 24) | (byte4 << 32) | (byte3 << 40) | (byte2 << 48) | (byte1 << 56)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt64BigEndian, readInt64 + */ + virtual int64 readInt64BigEndian(); + + /** Reads four bytes as a 32-bit floating point value. + + The raw 32-bit encoding of the float is read from the stream as a little-endian int. + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeFloat, readDouble + */ + virtual float readFloat(); + + /** Reads four bytes as a 32-bit floating point value. + + The raw 32-bit encoding of the float is read from the stream as a big-endian int. + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeFloatBigEndian, readDoubleBigEndian + */ + virtual float readFloatBigEndian(); + + /** Reads eight bytes as a 64-bit floating point value. + + The raw 64-bit encoding of the double is read from the stream as a little-endian int64. + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeDouble, readFloat + */ + virtual double readDouble(); + + /** Reads eight bytes as a 64-bit floating point value. + + The raw 64-bit encoding of the double is read from the stream as a big-endian int64. + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeDoubleBigEndian, readFloatBigEndian + */ + virtual double readDoubleBigEndian(); + + /** Reads an encoded 32-bit number from the stream using a space-saving compressed format. + + For small values, this is more space-efficient than using readInt() and OutputStream::writeInt() + + The format used is: number of significant bytes + up to 4 bytes in little-endian order. + + @see OutputStream::writeCompressedInt() + */ + virtual int readCompressedInt(); + + //============================================================================== + /** Reads a UTF-8 string from the stream, up to the next linefeed or carriage return. + + This will read up to the next "\n" or "\r\n" or end-of-stream. + + After this call, the stream's position will be left pointing to the next character + following the line-feed, but the linefeeds aren't included in the string that + is returned. + */ + virtual String readNextLine(); + + /** Reads a zero-terminated UTF-8 string from the stream. + + This will read characters from the stream until it hits a null character + or end-of-stream. + + @see OutputStream::writeString, readEntireStreamAsString + */ + virtual String readString(); + + /** Tries to read the whole stream and turn it into a string. + + This will read from the stream's current position until the end-of-stream. + It can read from either UTF-16 or UTF-8 formats. + */ + virtual String readEntireStreamAsString(); + + /** Reads from the stream and appends the data to a MemoryBlock. + + @param destBlock the block to append the data onto + @param maxNumBytesToRead if this is a positive value, it sets a limit to the number + of bytes that will be read - if it's negative, data + will be read until the stream is exhausted. + @returns the number of bytes that were added to the memory block + */ + virtual int readIntoMemoryBlock (MemoryBlock& destBlock, + ssize_t maxNumBytesToRead = -1); + + //============================================================================== + /** Returns the offset of the next byte that will be read from the stream. + + @see setPosition + */ + virtual int64 getPosition() = 0; + + /** Tries to move the current read position of the stream. + + The position is an absolute number of bytes from the stream's start. + + Some streams might not be able to do this, in which case they should do + nothing and return false. Others might be able to manage it by resetting + themselves and skipping to the correct position, although this is + obviously a bit slow. + + @returns true if the stream manages to reposition itself correctly + @see getPosition + */ + virtual bool setPosition (int64 newPosition) = 0; + + /** Reads and discards a number of bytes from the stream. + + Some input streams might implement this efficiently, but the base + class will just keep reading data until the requisite number of bytes + have been done. + */ + virtual void skipNextBytes (int64 numBytesToSkip); + + +protected: + //============================================================================== + InputStream() noexcept {} + +private: + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputStream) +}; + +#endif // BEAST_INPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/streams/beast_MemoryInputStream.cpp b/modules/beast_core/streams/beast_MemoryInputStream.cpp new file mode 100644 index 0000000000..d14900e3b4 --- /dev/null +++ b/modules/beast_core/streams/beast_MemoryInputStream.cpp @@ -0,0 +1,155 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +MemoryInputStream::MemoryInputStream (const void* const sourceData, + const size_t sourceDataSize, + const bool keepInternalCopy) + : data (sourceData), + dataSize (sourceDataSize), + position (0) +{ + if (keepInternalCopy) + createInternalCopy(); +} + +MemoryInputStream::MemoryInputStream (const MemoryBlock& sourceData, + const bool keepInternalCopy) + : data (sourceData.getData()), + dataSize (sourceData.getSize()), + position (0) +{ + if (keepInternalCopy) + createInternalCopy(); +} + +void MemoryInputStream::createInternalCopy() +{ + internalCopy.malloc (dataSize); + memcpy (internalCopy, data, dataSize); + data = internalCopy; +} + +MemoryInputStream::~MemoryInputStream() +{ +} + +int64 MemoryInputStream::getTotalLength() +{ + return dataSize; +} + +int MemoryInputStream::read (void* const buffer, const int howMany) +{ + bassert (buffer != nullptr && howMany >= 0); + + const int num = bmin (howMany, (int) (dataSize - position)); + if (num <= 0) + return 0; + + memcpy (buffer, addBytesToPointer (data, position), (size_t) num); + position += (unsigned int) num; + return num; +} + +bool MemoryInputStream::isExhausted() +{ + return position >= dataSize; +} + +bool MemoryInputStream::setPosition (const int64 pos) +{ + position = (size_t) blimit ((int64) 0, (int64) dataSize, pos); + return true; +} + +int64 MemoryInputStream::getPosition() +{ + return position; +} + + +//============================================================================== +#if BEAST_UNIT_TESTS + +class MemoryStreamTests : public UnitTest +{ +public: + MemoryStreamTests() : UnitTest ("MemoryInputStream & MemoryOutputStream") {} + + void runTest() + { + beginTest ("Basics"); + Random r; + + int randomInt = r.nextInt(); + int64 randomInt64 = r.nextInt64(); + double randomDouble = r.nextDouble(); + String randomString (createRandomWideCharString()); + + MemoryOutputStream mo; + mo.writeInt (randomInt); + mo.writeIntBigEndian (randomInt); + mo.writeCompressedInt (randomInt); + mo.writeString (randomString); + mo.writeInt64 (randomInt64); + mo.writeInt64BigEndian (randomInt64); + mo.writeDouble (randomDouble); + mo.writeDoubleBigEndian (randomDouble); + + MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); + expect (mi.readInt() == randomInt); + expect (mi.readIntBigEndian() == randomInt); + expect (mi.readCompressedInt() == randomInt); + expectEquals (mi.readString(), randomString); + expect (mi.readInt64() == randomInt64); + expect (mi.readInt64BigEndian() == randomInt64); + expect (mi.readDouble() == randomDouble); + expect (mi.readDoubleBigEndian() == randomDouble); + } + + static String createRandomWideCharString() + { + beast_wchar buffer [50] = { 0 }; + Random r; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (beast_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (beast_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } +}; + +static MemoryStreamTests memoryInputStreamUnitTests; + +#endif diff --git a/modules/beast_core/streams/beast_MemoryInputStream.h b/modules/beast_core/streams/beast_MemoryInputStream.h new file mode 100644 index 0000000000..ce39cc7897 --- /dev/null +++ b/modules/beast_core/streams/beast_MemoryInputStream.h @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MEMORYINPUTSTREAM_BEASTHEADER +#define BEAST_MEMORYINPUTSTREAM_BEASTHEADER + +#include "beast_InputStream.h" +#include "../memory/beast_HeapBlock.h" + + +//============================================================================== +/** + Allows a block of data to be accessed as a stream. + + This can either be used to refer to a shared block of memory, or can make its + own internal copy of the data when the MemoryInputStream is created. +*/ +class BEAST_API MemoryInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a MemoryInputStream. + + @param sourceData the block of data to use as the stream's source + @param sourceDataSize the number of bytes in the source data block + @param keepInternalCopyOfData if false, the stream will just keep a pointer to + the source data, so this data shouldn't be changed + for the lifetime of the stream; if this parameter is + true, the stream will make its own copy of the + data and use that. + */ + MemoryInputStream (const void* sourceData, + size_t sourceDataSize, + bool keepInternalCopyOfData); + + /** Creates a MemoryInputStream. + + @param data a block of data to use as the stream's source + @param keepInternalCopyOfData if false, the stream will just keep a reference to + the source data, so this data shouldn't be changed + for the lifetime of the stream; if this parameter is + true, the stream will make its own copy of the + data and use that. + */ + MemoryInputStream (const MemoryBlock& data, + bool keepInternalCopyOfData); + + /** Destructor. */ + ~MemoryInputStream(); + + /** Returns a pointer to the source data block from which this stream is reading. */ + const void* getData() const noexcept { return data; } + + /** Returns the number of bytes of source data in the block from which this stream is reading. */ + size_t getDataSize() const noexcept { return dataSize; } + + //============================================================================== + int64 getPosition(); + bool setPosition (int64 pos); + int64 getTotalLength(); + bool isExhausted(); + int read (void* destBuffer, int maxBytesToRead); + +private: + //============================================================================== + const void* data; + size_t dataSize, position; + HeapBlock internalCopy; + + void createInternalCopy(); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryInputStream) +}; + +#endif // BEAST_MEMORYINPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/streams/beast_MemoryOutputStream.cpp b/modules/beast_core/streams/beast_MemoryOutputStream.cpp new file mode 100644 index 0000000000..0505920614 --- /dev/null +++ b/modules/beast_core/streams/beast_MemoryOutputStream.cpp @@ -0,0 +1,165 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +MemoryOutputStream::MemoryOutputStream (const size_t initialSize) + : data (internalBlock), + position (0), + size (0) +{ + internalBlock.setSize (initialSize, false); +} + +MemoryOutputStream::MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + const bool appendToExistingBlockContent) + : data (memoryBlockToWriteTo), + position (0), + size (0) +{ + if (appendToExistingBlockContent) + position = size = memoryBlockToWriteTo.getSize(); +} + +MemoryOutputStream::~MemoryOutputStream() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::flush() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::trimExternalBlockSize() +{ + if (&data != &internalBlock) + data.setSize (size, false); +} + +void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) +{ + data.ensureSize (bytesToPreallocate + 1); +} + +void MemoryOutputStream::reset() noexcept +{ + position = 0; + size = 0; +} + +char* MemoryOutputStream::prepareToWrite (size_t numBytes) +{ + bassert ((ssize_t) numBytes >= 0); + size_t storageNeeded = position + numBytes; + + if (storageNeeded >= data.getSize()) + data.ensureSize ((storageNeeded + bmin (storageNeeded / 2, (size_t) (1024 * 1024)) + 32) & ~31u); + + char* const writePointer = static_cast (data.getData()) + position; + position += numBytes; + size = bmax (size, position); + return writePointer; +} + +bool MemoryOutputStream::write (const void* const buffer, size_t howMany) +{ + bassert (buffer != nullptr && ((ssize_t) howMany) >= 0); + + if (howMany > 0) + memcpy (prepareToWrite (howMany), buffer, howMany); + + return true; +} + +void MemoryOutputStream::writeRepeatedByte (uint8 byte, size_t howMany) +{ + if (howMany > 0) + memset (prepareToWrite (howMany), byte, howMany); +} + +void MemoryOutputStream::appendUTF8Char (beast_wchar c) +{ + CharPointer_UTF8 (prepareToWrite (CharPointer_UTF8::getBytesRequiredFor (c))).write (c); +} + +MemoryBlock MemoryOutputStream::getMemoryBlock() const +{ + return MemoryBlock (getData(), getDataSize()); +} + +const void* MemoryOutputStream::getData() const noexcept +{ + if (data.getSize() > size) + static_cast (data.getData()) [size] = 0; + + return data.getData(); +} + +bool MemoryOutputStream::setPosition (int64 newPosition) +{ + if (newPosition <= (int64) size) + { + // ok to seek backwards + position = blimit ((size_t) 0, size, (size_t) newPosition); + return true; + } + + // can't move beyond the end of the stream.. + return false; +} + +int MemoryOutputStream::writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite) +{ + // before writing from an input, see if we can preallocate to make it more efficient.. + int64 availableData = source.getTotalLength() - source.getPosition(); + + if (availableData > 0) + { + if (maxNumBytesToWrite > availableData) + maxNumBytesToWrite = availableData; + + preallocate (data.getSize() + (size_t) maxNumBytesToWrite); + } + + return OutputStream::writeFromInputStream (source, maxNumBytesToWrite); +} + +String MemoryOutputStream::toUTF8() const +{ + const char* const d = static_cast (getData()); + return String (CharPointer_UTF8 (d), CharPointer_UTF8 (d + getDataSize())); +} + +String MemoryOutputStream::toString() const +{ + return String::createStringFromData (getData(), (int) getDataSize()); +} + +OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead) +{ + const size_t dataSize = streamToRead.getDataSize(); + + if (dataSize > 0) + stream.write (streamToRead.getData(), dataSize); + + return stream; +} diff --git a/modules/beast_core/streams/beast_MemoryOutputStream.h b/modules/beast_core/streams/beast_MemoryOutputStream.h new file mode 100644 index 0000000000..98ea3daf90 --- /dev/null +++ b/modules/beast_core/streams/beast_MemoryOutputStream.h @@ -0,0 +1,131 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_MEMORYOUTPUTSTREAM_BEASTHEADER +#define BEAST_MEMORYOUTPUTSTREAM_BEASTHEADER + +#include "beast_OutputStream.h" +#include "../memory/beast_MemoryBlock.h" +#include "../memory/beast_ScopedPointer.h" + + +//============================================================================== +/** + Writes data to an internal memory buffer, which grows as required. + + The data that was written into the stream can then be accessed later as + a contiguous block of memory. +*/ +class BEAST_API MemoryOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates an empty memory stream, ready to be written into. + + @param initialSize the intial amount of capacity to allocate for writing into + */ + MemoryOutputStream (size_t initialSize = 256); + + /** Creates a memory stream for writing into into a pre-existing MemoryBlock object. + + Note that the destination block will always be larger than the amount of data + that has been written to the stream, because the MemoryOutputStream keeps some + spare capactity at its end. To trim the block's size down to fit the actual + data, call flush(), or delete the MemoryOutputStream. + + @param memoryBlockToWriteTo the block into which new data will be written. + @param appendToExistingBlockContent if this is true, the contents of the block will be + kept, and new data will be appended to it. If false, + the block will be cleared before use + */ + MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + bool appendToExistingBlockContent); + + /** Destructor. + This will free any data that was written to it. + */ + ~MemoryOutputStream(); + + //============================================================================== + /** Returns a pointer to the data that has been written to the stream. + @see getDataSize + */ + const void* getData() const noexcept; + + /** Returns the number of bytes of data that have been written to the stream. + @see getData + */ + size_t getDataSize() const noexcept { return size; } + + /** Resets the stream, clearing any data that has been written to it so far. */ + void reset() noexcept; + + /** Increases the internal storage capacity to be able to contain at least the specified + amount of data without needing to be resized. + */ + void preallocate (size_t bytesToPreallocate); + + /** Appends the utf-8 bytes for a unicode character */ + void appendUTF8Char (beast_wchar character); + + /** Returns a String created from the (UTF8) data that has been written to the stream. */ + String toUTF8() const; + + /** Attempts to detect the encoding of the data and convert it to a string. + @see String::createStringFromData + */ + String toString() const; + + /** Returns a copy of the stream's data as a memory block. */ + MemoryBlock getMemoryBlock() const; + + //============================================================================== + /** If the stream is writing to a user-supplied MemoryBlock, this will trim any excess + capacity off the block, so that its length matches the amount of actual data that + has been written so far. + */ + void flush(); + + bool write (const void* buffer, size_t howMany); + int64 getPosition() { return position; } + bool setPosition (int64 newPosition); + int writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite); + void writeRepeatedByte (uint8 byte, size_t numTimesToRepeat); + +private: + //============================================================================== + MemoryBlock& data; + MemoryBlock internalBlock; + size_t position, size; + + void trimExternalBlockSize(); + char* prepareToWrite (size_t); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryOutputStream) +}; + +/** Copies all the data that has been written to a MemoryOutputStream into another stream. */ +OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead); + + +#endif // BEAST_MEMORYOUTPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/streams/beast_OutputStream.cpp b/modules/beast_core/streams/beast_OutputStream.cpp new file mode 100644 index 0000000000..5089153779 --- /dev/null +++ b/modules/beast_core/streams/beast_OutputStream.cpp @@ -0,0 +1,320 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#if BEAST_DEBUG + +struct DanglingStreamChecker +{ + DanglingStreamChecker() {} + + ~DanglingStreamChecker() + { + /* + It's always a bad idea to leak any object, but if you're leaking output + streams, then there's a good chance that you're failing to flush a file + to disk properly, which could result in corrupted data and other similar + nastiness.. + */ + bassert (activeStreams.size() == 0); + } + + Array activeStreams; +}; + +static DanglingStreamChecker danglingStreamChecker; +#endif + +//============================================================================== +OutputStream::OutputStream() + : newLineString (NewLine::getDefault()) +{ + #if BEAST_DEBUG + danglingStreamChecker.activeStreams.add (this); + #endif +} + +OutputStream::~OutputStream() +{ + #if BEAST_DEBUG + danglingStreamChecker.activeStreams.removeFirstMatchingValue (this); + #endif +} + +//============================================================================== +void OutputStream::writeBool (const bool b) +{ + writeByte (b ? (char) 1 + : (char) 0); +} + +void OutputStream::writeByte (char byte) +{ + write (&byte, 1); +} + +void OutputStream::writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) +{ + for (size_t i = 0; i < numTimesToRepeat; ++i) + writeByte ((char) byte); +} + +void OutputStream::writeShort (short value) +{ + const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); + write (&v, 2); +} + +void OutputStream::writeShortBigEndian (short value) +{ + const unsigned short v = ByteOrder::swapIfLittleEndian ((unsigned short) value); + write (&v, 2); +} + +void OutputStream::writeInt (int value) +{ + const unsigned int v = ByteOrder::swapIfBigEndian ((unsigned int) value); + write (&v, 4); +} + +void OutputStream::writeIntBigEndian (int value) +{ + const unsigned int v = ByteOrder::swapIfLittleEndian ((unsigned int) value); + write (&v, 4); +} + +void OutputStream::writeCompressedInt (int value) +{ + unsigned int un = (value < 0) ? (unsigned int) -value + : (unsigned int) value; + + uint8 data[5]; + int num = 0; + + while (un > 0) + { + data[++num] = (uint8) un; + un >>= 8; + } + + data[0] = (uint8) num; + + if (value < 0) + data[0] |= 0x80; + + write (data, num + 1); +} + +void OutputStream::writeInt64 (int64 value) +{ + const uint64 v = ByteOrder::swapIfBigEndian ((uint64) value); + write (&v, 8); +} + +void OutputStream::writeInt64BigEndian (int64 value) +{ + const uint64 v = ByteOrder::swapIfLittleEndian ((uint64) value); + write (&v, 8); +} + +void OutputStream::writeFloat (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + writeInt (n.asInt); +} + +void OutputStream::writeFloatBigEndian (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + writeIntBigEndian (n.asInt); +} + +void OutputStream::writeDouble (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + writeInt64 (n.asInt); +} + +void OutputStream::writeDoubleBigEndian (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + writeInt64BigEndian (n.asInt); +} + +void OutputStream::writeString (const String& text) +{ + // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind + // if lots of large, persistent strings were to be written to streams). + const size_t numBytes = text.getNumBytesAsUTF8() + 1; + HeapBlock temp (numBytes); + text.copyToUTF8 (temp, numBytes); + write (temp, numBytes); +} + +void OutputStream::writeText (const String& text, const bool asUTF16, + const bool writeUTF16ByteOrderMark) +{ + if (asUTF16) + { + if (writeUTF16ByteOrderMark) + write ("\x0ff\x0fe", 2); + + String::CharPointerType src (text.getCharPointer()); + bool lastCharWasReturn = false; + + for (;;) + { + const beast_wchar c = src.getAndAdvance(); + + if (c == 0) + break; + + if (c == '\n' && ! lastCharWasReturn) + writeShort ((short) '\r'); + + lastCharWasReturn = (c == L'\r'); + writeShort ((short) c); + } + } + else + { + const char* src = text.toUTF8(); + const char* t = src; + + for (;;) + { + if (*t == '\n') + { + if (t > src) + write (src, (int) (t - src)); + + write ("\r\n", 2); + src = t + 1; + } + else if (*t == '\r') + { + if (t[1] == '\n') + ++t; + } + else if (*t == 0) + { + if (t > src) + write (src, (int) (t - src)); + + break; + } + + ++t; + } + } +} + +int OutputStream::writeFromInputStream (InputStream& source, int64 numBytesToWrite) +{ + if (numBytesToWrite < 0) + numBytesToWrite = std::numeric_limits::max(); + + int numWritten = 0; + + while (numBytesToWrite > 0) + { + char buffer [8192]; + const int num = source.read (buffer, (int) bmin (numBytesToWrite, (int64) sizeof (buffer))); + + if (num <= 0) + break; + + write (buffer, num); + + numBytesToWrite -= num; + numWritten += num; + } + + return numWritten; +} + +//============================================================================== +void OutputStream::setNewLineString (const String& newLineString_) +{ + newLineString = newLineString_; +} + +//============================================================================== +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const int number) +{ + return stream << String (number); +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const int64 number) +{ + return stream << String (number); +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const double number) +{ + return stream << String (number); +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const char character) +{ + stream.writeByte (character); + return stream; +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const char* const text) +{ + stream.write (text, strlen (text)); + return stream; +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data) +{ + if (data.getSize() > 0) + stream.write (data.getData(), data.getSize()); + + return stream; +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) +{ + FileInputStream in (fileToRead); + + if (in.openedOk()) + return stream << in; + + return stream; +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead) +{ + stream.writeFromInputStream (streamToRead, -1); + return stream; +} + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const NewLine&) +{ + return stream << stream.getNewLineString(); +} diff --git a/modules/beast_core/streams/beast_OutputStream.h b/modules/beast_core/streams/beast_OutputStream.h new file mode 100644 index 0000000000..052cbc4fc7 --- /dev/null +++ b/modules/beast_core/streams/beast_OutputStream.h @@ -0,0 +1,262 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_OUTPUTSTREAM_BEASTHEADER +#define BEAST_OUTPUTSTREAM_BEASTHEADER + +#include "../text/beast_String.h" +#include "../text/beast_NewLine.h" +class InputStream; +class MemoryBlock; +class File; + + +//============================================================================== +/** + The base class for streams that write data to some kind of destination. + + Input and output streams are used throughout the library - subclasses can override + some or all of the virtual functions to implement their behaviour. + + @see InputStream, MemoryOutputStream, FileOutputStream +*/ +class BEAST_API OutputStream +{ +protected: + //============================================================================== + OutputStream(); + +public: + /** Destructor. + + Some subclasses might want to do things like call flush() during their + destructors. + */ + virtual ~OutputStream(); + + //============================================================================== + /** If the stream is using a buffer, this will ensure it gets written + out to the destination. */ + virtual void flush() = 0; + + /** Tries to move the stream's output position. + + Not all streams will be able to seek to a new position - this will return + false if it fails to work. + + @see getPosition + */ + virtual bool setPosition (int64 newPosition) = 0; + + /** Returns the stream's current position. + + @see setPosition + */ + virtual int64 getPosition() = 0; + + //============================================================================== + /** Writes a block of data to the stream. + + When creating a subclass of OutputStream, this is the only write method + that needs to be overloaded - the base class has methods for writing other + types of data which use this to do the work. + + @param dataToWrite the target buffer to receive the data. This must not be null. + @param numberOfBytes the number of bytes to write. + @returns false if the write operation fails for some reason + */ + virtual bool write (const void* dataToWrite, + size_t numberOfBytes) = 0; + + //============================================================================== + /** Writes a single byte to the stream. + + @see InputStream::readByte + */ + virtual void writeByte (char byte); + + /** Writes a boolean to the stream as a single byte. + This is encoded as a binary byte (not as text) with a value of 1 or 0. + @see InputStream::readBool + */ + virtual void writeBool (bool boolValue); + + /** Writes a 16-bit integer to the stream in a little-endian byte order. + This will write two bytes to the stream: (value & 0xff), then (value >> 8). + @see InputStream::readShort + */ + virtual void writeShort (short value); + + /** Writes a 16-bit integer to the stream in a big-endian byte order. + This will write two bytes to the stream: (value >> 8), then (value & 0xff). + @see InputStream::readShortBigEndian + */ + virtual void writeShortBigEndian (short value); + + /** Writes a 32-bit integer to the stream in a little-endian byte order. + @see InputStream::readInt + */ + virtual void writeInt (int value); + + /** Writes a 32-bit integer to the stream in a big-endian byte order. + @see InputStream::readIntBigEndian + */ + virtual void writeIntBigEndian (int value); + + /** Writes a 64-bit integer to the stream in a little-endian byte order. + @see InputStream::readInt64 + */ + virtual void writeInt64 (int64 value); + + /** Writes a 64-bit integer to the stream in a big-endian byte order. + @see InputStream::readInt64BigEndian + */ + virtual void writeInt64BigEndian (int64 value); + + /** Writes a 32-bit floating point value to the stream in a binary format. + The binary 32-bit encoding of the float is written as a little-endian int. + @see InputStream::readFloat + */ + virtual void writeFloat (float value); + + /** Writes a 32-bit floating point value to the stream in a binary format. + The binary 32-bit encoding of the float is written as a big-endian int. + @see InputStream::readFloatBigEndian + */ + virtual void writeFloatBigEndian (float value); + + /** Writes a 64-bit floating point value to the stream in a binary format. + The eight raw bytes of the double value are written out as a little-endian 64-bit int. + @see InputStream::readDouble + */ + virtual void writeDouble (double value); + + /** Writes a 64-bit floating point value to the stream in a binary format. + The eight raw bytes of the double value are written out as a big-endian 64-bit int. + @see InputStream::readDoubleBigEndian + */ + virtual void writeDoubleBigEndian (double value); + + /** Writes a byte to the output stream a given number of times. */ + virtual void writeRepeatedByte (uint8 byte, size_t numTimesToRepeat); + + /** Writes a condensed binary encoding of a 32-bit integer. + + If you're storing a lot of integers which are unlikely to have very large values, + this can save a lot of space, because values under 0xff will only take up 2 bytes, + under 0xffff only 3 bytes, etc. + + The format used is: number of significant bytes + up to 4 bytes in little-endian order. + + @see InputStream::readCompressedInt + */ + virtual void writeCompressedInt (int value); + + /** Stores a string in the stream in a binary format. + + This isn't the method to use if you're trying to append text to the end of a + text-file! It's intended for storing a string so that it can be retrieved later + by InputStream::readString(). + + It writes the string to the stream as UTF8, including the null termination character. + + For appending text to a file, instead use writeText, or operator<< + + @see InputStream::readString, writeText, operator<< + */ + virtual void writeString (const String& text); + + /** Writes a string of text to the stream. + + It can either write the text as UTF-8 or UTF-16, and can also add the UTF-16 byte-order-mark + bytes (0xff, 0xfe) to indicate the endianness (these should only be used at the start + of a file). + + The method also replaces '\\n' characters in the text with '\\r\\n'. + */ + virtual void writeText (const String& text, + bool asUTF16, + bool writeUTF16ByteOrderMark); + + /** Reads data from an input stream and writes it to this stream. + + @param source the stream to read from + @param maxNumBytesToWrite the number of bytes to read from the stream (if this is + less than zero, it will keep reading until the input + is exhausted) + */ + virtual int writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite); + + //============================================================================== + /** Sets the string that will be written to the stream when the writeNewLine() + method is called. + By default this will be set the the value of NewLine::getDefault(). + */ + void setNewLineString (const String& newLineString); + + /** Returns the current new-line string that was set by setNewLineString(). */ + const String& getNewLineString() const noexcept { return newLineString; } + +private: + //============================================================================== + String newLineString; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OutputStream) +}; + +//============================================================================== +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, int number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, int64 number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, double number); + +/** Writes a character to a stream. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, char character); + +/** Writes a null-terminated text string to a stream. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const char* text); + +/** Writes a block of data from a MemoryBlock to a stream. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data); + +/** Writes the contents of a file to a stream. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead); + +/** Writes the complete contents of an input stream to an output stream. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead); + +/** Writes a new-line to a stream. + You can use the predefined symbol 'newLine' to invoke this, e.g. + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode + @see OutputStream::setNewLineString +*/ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const NewLine&); + + +#endif // BEAST_OUTPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/streams/beast_SubregionStream.cpp b/modules/beast_core/streams/beast_SubregionStream.cpp new file mode 100644 index 0000000000..158b99a06c --- /dev/null +++ b/modules/beast_core/streams/beast_SubregionStream.cpp @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +SubregionStream::SubregionStream (InputStream* const sourceStream, + const int64 start, const int64 length, + const bool deleteSourceWhenDestroyed) + : source (sourceStream, deleteSourceWhenDestroyed), + startPositionInSourceStream (start), + lengthOfSourceStream (length) +{ + SubregionStream::setPosition (0); +} + +SubregionStream::~SubregionStream() +{ +} + +int64 SubregionStream::getTotalLength() +{ + const int64 srcLen = source->getTotalLength() - startPositionInSourceStream; + + return lengthOfSourceStream >= 0 ? bmin (lengthOfSourceStream, srcLen) + : srcLen; +} + +int64 SubregionStream::getPosition() +{ + return source->getPosition() - startPositionInSourceStream; +} + +bool SubregionStream::setPosition (int64 newPosition) +{ + return source->setPosition (bmax ((int64) 0, newPosition + startPositionInSourceStream)); +} + +int SubregionStream::read (void* destBuffer, int maxBytesToRead) +{ + bassert (destBuffer != nullptr && maxBytesToRead >= 0); + + if (lengthOfSourceStream < 0) + return source->read (destBuffer, maxBytesToRead); + + maxBytesToRead = (int) bmin ((int64) maxBytesToRead, lengthOfSourceStream - getPosition()); + + if (maxBytesToRead <= 0) + return 0; + + return source->read (destBuffer, maxBytesToRead); +} + +bool SubregionStream::isExhausted() +{ + if (lengthOfSourceStream >= 0 && getPosition() >= lengthOfSourceStream) + return true; + + return source->isExhausted(); +} diff --git a/modules/beast_core/streams/beast_SubregionStream.h b/modules/beast_core/streams/beast_SubregionStream.h new file mode 100644 index 0000000000..f98cdecfe5 --- /dev/null +++ b/modules/beast_core/streams/beast_SubregionStream.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SUBREGIONSTREAM_BEASTHEADER +#define BEAST_SUBREGIONSTREAM_BEASTHEADER + +#include "beast_InputStream.h" +#include "../memory/beast_OptionalScopedPointer.h" + + +//============================================================================== +/** Wraps another input stream, and reads from a specific part of it. + + This lets you take a subsection of a stream and present it as an entire + stream in its own right. +*/ +class BEAST_API SubregionStream : public InputStream +{ +public: + //============================================================================== + /** Creates a SubregionStream from an input source. + + @param sourceStream the source stream to read from + @param startPositionInSourceStream this is the position in the source stream that + corresponds to position 0 in this stream + @param lengthOfSourceStream this specifies the maximum number of bytes + from the source stream that will be passed through + by this stream. When the position of this stream + exceeds lengthOfSourceStream, it will cause an end-of-stream. + If the length passed in here is greater than the length + of the source stream (as returned by getTotalLength()), + then the smaller value will be used. + Passing a negative value for this parameter means it + will keep reading until the source's end-of-stream. + @param deleteSourceWhenDestroyed whether the sourceStream that is passed in should be + deleted by this object when it is itself deleted. + */ + SubregionStream (InputStream* sourceStream, + int64 startPositionInSourceStream, + int64 lengthOfSourceStream, + bool deleteSourceWhenDestroyed); + + /** Destructor. + + This may also delete the source stream, if that option was chosen when the + buffered stream was created. + */ + ~SubregionStream(); + + + //============================================================================== + int64 getTotalLength(); + int64 getPosition(); + bool setPosition (int64 newPosition); + int read (void* destBuffer, int maxBytesToRead); + bool isExhausted(); + + + //============================================================================== +private: + OptionalScopedPointer source; + const int64 startPositionInSourceStream, lengthOfSourceStream; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubregionStream) +}; + +#endif // BEAST_SUBREGIONSTREAM_BEASTHEADER diff --git a/modules/beast_core/system/beast_PlatformDefs.h b/modules/beast_core/system/beast_PlatformDefs.h new file mode 100644 index 0000000000..9c1475ba58 --- /dev/null +++ b/modules/beast_core/system/beast_PlatformDefs.h @@ -0,0 +1,352 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_PLATFORMDEFS_BEASTHEADER +#define BEAST_PLATFORMDEFS_BEASTHEADER + +//============================================================================== +/* This file defines miscellaneous macros for debugging, assertions, etc. +*/ + +//============================================================================== +#ifdef BEAST_FORCE_DEBUG + #undef BEAST_DEBUG + + #if BEAST_FORCE_DEBUG + #define BEAST_DEBUG 1 + #endif +#endif + +/** This macro defines the C calling convention used as the standard for Beast calls. */ +#if BEAST_MSVC + #define BEAST_CALLTYPE __stdcall + #define BEAST_CDECL __cdecl +#else + #define BEAST_CALLTYPE + #define BEAST_CDECL +#endif + +//============================================================================== +// Debugging and assertion macros + +#if BEAST_LOG_ASSERTIONS || BEAST_DEBUG + #define beast_LogCurrentAssertion beast::logAssertion (__FILE__, __LINE__); +#else + #define beast_LogCurrentAssertion +#endif + +//============================================================================== +#if BEAST_IOS || BEAST_LINUX || BEAST_ANDROID || BEAST_PPC + /** This will try to break into the debugger if the app is currently being debugged. + If called by an app that's not being debugged, the behaiour isn't defined - it may crash or not, depending + on the platform. + @see bassert() + */ + #define beast_breakDebugger { ::kill (0, SIGTRAP); } +#elif BEAST_USE_INTRINSICS + #ifndef __INTEL_COMPILER + #pragma intrinsic (__debugbreak) + #endif + #define beast_breakDebugger { __debugbreak(); } +#elif BEAST_GCC || BEAST_MAC + #if BEAST_NO_INLINE_ASM + #define beast_breakDebugger { } + #else + #define beast_breakDebugger { asm ("int $3"); } + #endif +#else + #define beast_breakDebugger { __asm int 3 } +#endif + + +//============================================================================== +#if BEAST_DEBUG || DOXYGEN + /** Writes a string to the standard error stream. + This is only compiled in a debug build. + @see Logger::outputDebugString + */ + #define DBG(dbgtext) { beast::String tempDbgBuf; tempDbgBuf << dbgtext; beast::Logger::outputDebugString (tempDbgBuf); } + + //============================================================================== + /** This will always cause an assertion failure. + It is only compiled in a debug build, (unless BEAST_LOG_ASSERTIONS is enabled for your build). + @see bassert + */ + #define jassertfalse { beast_LogCurrentAssertion; if (beast::beast_isRunningUnderDebugger()) beast_breakDebugger; } + + //============================================================================== + /** Platform-independent assertion macro. + + This macro gets turned into a no-op when you're building with debugging turned off, so be + careful that the expression you pass to it doesn't perform any actions that are vital for the + correct behaviour of your program! + @see jassertfalse + */ + #define bassert(expression) { if (! (expression)) jassertfalse; } + +#else + //============================================================================== + // If debugging is disabled, these dummy debug and assertion macros are used.. + + #define DBG(dbgtext) + #define jassertfalse { beast_LogCurrentAssertion } + + #if BEAST_LOG_ASSERTIONS + #define bassert(expression) { if (! (expression)) jassertfalse; } + #else + #define bassert(a) {} + #endif + +#endif + +//============================================================================== +#ifndef DOXYGEN +namespace beast +{ + template struct BeastStaticAssert; + template <> struct BeastStaticAssert { static void dummy() {} }; +} +#endif + +/** A compile-time assertion macro. + If the expression parameter is false, the macro will cause a compile error. (The actual error + message that the compiler generates may be completely bizarre and seem to have no relation to + the place where you put the static_assert though!) +*/ +#define static_bassert(expression) beast::BeastStaticAssert::dummy(); + +/** This is a shorthand macro for declaring stubs for a class's copy constructor and operator=. + + For example, instead of + @code + class MyClass + { + etc.. + + private: + MyClass (const MyClass&); + MyClass& operator= (const MyClass&); + };@endcode + + ..you can just write: + + @code + class MyClass + { + etc.. + + private: + BEAST_DECLARE_NON_COPYABLE (MyClass) + };@endcode +*/ +#define BEAST_DECLARE_NON_COPYABLE(className) \ + className (const className&);\ + className& operator= (const className&); + +/** This is a shorthand way of writing both a BEAST_DECLARE_NON_COPYABLE and + BEAST_LEAK_DETECTOR macro for a class. +*/ +#define BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className) \ + BEAST_DECLARE_NON_COPYABLE(className) \ + BEAST_LEAK_DETECTOR(className) + +/** This macro can be added to class definitions to disable the use of new/delete to + allocate the object on the heap, forcing it to only be used as a stack or member variable. +*/ +#define BEAST_PREVENT_HEAP_ALLOCATION \ + private: \ + static void* operator new (size_t); \ + static void operator delete (void*); + + +//============================================================================== +#if ! DOXYGEN + #define BEAST_JOIN_MACRO_HELPER(a, b) a ## b + #define BEAST_STRINGIFY_MACRO_HELPER(a) #a +#endif + +/** A good old-fashioned C macro concatenation helper. + This combines two items (which may themselves be macros) into a single string, + avoiding the pitfalls of the ## macro operator. +*/ +#define BEAST_JOIN_MACRO(item1, item2) BEAST_JOIN_MACRO_HELPER (item1, item2) + +/** A handy C macro for stringifying any symbol, rather than just a macro parameter. +*/ +#define BEAST_STRINGIFY(item) BEAST_STRINGIFY_MACRO_HELPER (item) + + +//============================================================================== +#if BEAST_CATCH_UNHANDLED_EXCEPTIONS + + #define BEAST_TRY try + + #define BEAST_CATCH_ALL catch (...) {} + #define BEAST_CATCH_ALL_ASSERT catch (...) { jassertfalse; } + + #if ! BEAST_MODULE_AVAILABLE_beast_gui_basics + #define BEAST_CATCH_EXCEPTION BEAST_CATCH_ALL + #else + /** Used in try-catch blocks, this macro will send exceptions to the BEASTApplication + object so they can be logged by the application if it wants to. + */ + #define BEAST_CATCH_EXCEPTION \ + catch (const std::exception& e) \ + { \ + beast::BEASTApplication::sendUnhandledException (&e, __FILE__, __LINE__); \ + } \ + catch (...) \ + { \ + beast::BEASTApplication::sendUnhandledException (nullptr, __FILE__, __LINE__); \ + } + #endif + +#else + + #define BEAST_TRY + #define BEAST_CATCH_EXCEPTION + #define BEAST_CATCH_ALL + #define BEAST_CATCH_ALL_ASSERT + +#endif + +//============================================================================== +#if BEAST_DEBUG || DOXYGEN + /** A platform-independent way of forcing an inline function. + Use the syntax: @code + forcedinline void myfunction (int x) + @endcode + */ + #define forcedinline inline +#else + #if BEAST_MSVC + #define forcedinline __forceinline + #else + #define forcedinline inline __attribute__((always_inline)) + #endif +#endif + +#if BEAST_MSVC || DOXYGEN + /** This can be placed before a stack or member variable declaration to tell the compiler + to align it to the specified number of bytes. */ + #define BEAST_ALIGN(bytes) __declspec (align (bytes)) +#else + #define BEAST_ALIGN(bytes) __attribute__ ((aligned (bytes))) +#endif + +//============================================================================== +// Cross-compiler deprecation macros.. +#if DOXYGEN || (BEAST_MSVC && ! BEAST_NO_DEPRECATION_WARNINGS) + /** This can be used to wrap a function which has been deprecated. */ + #define BEAST_DEPRECATED(functionDef) __declspec(deprecated) functionDef +#elif BEAST_GCC && ! BEAST_NO_DEPRECATION_WARNINGS + #define BEAST_DEPRECATED(functionDef) functionDef __attribute__ ((deprecated)) +#else + #define BEAST_DEPRECATED(functionDef) functionDef +#endif + +//============================================================================== +#if BEAST_ANDROID && ! DOXYGEN + #define BEAST_MODAL_LOOPS_PERMITTED 0 +#elif ! defined (BEAST_MODAL_LOOPS_PERMITTED) + /** Some operating environments don't provide a modal loop mechanism, so this flag can be + used to disable any functions that try to run a modal loop. */ + #define BEAST_MODAL_LOOPS_PERMITTED 1 +#endif + +//============================================================================== +#if BEAST_GCC + #define BEAST_PACKED __attribute__((packed)) +#elif ! DOXYGEN + #define BEAST_PACKED +#endif + +//============================================================================== +// Here, we'll check for C++11 compiler support, and if it's not available, define +// a few workarounds, so that we can still use some of the newer language features. +#if defined (__GXX_EXPERIMENTAL_CXX0X__) && defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 + #define BEAST_COMPILER_SUPPORTS_NOEXCEPT 1 + #define BEAST_COMPILER_SUPPORTS_NULLPTR 1 + #define BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (BEAST_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) + #define BEAST_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif +#endif + +#if BEAST_CLANG && defined (__has_feature) + #if __has_feature (cxx_nullptr) + #define BEAST_COMPILER_SUPPORTS_NULLPTR 1 + #endif + + #if __has_feature (cxx_noexcept) + #define BEAST_COMPILER_SUPPORTS_NOEXCEPT 1 + #endif + + #if __has_feature (cxx_rvalue_references) + #define BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + #endif + + #ifndef BEAST_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL + #define BEAST_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif + + #ifndef BEAST_COMPILER_SUPPORTS_ARC + #define BEAST_COMPILER_SUPPORTS_ARC 1 + #endif +#endif + +#if defined (_MSC_VER) && _MSC_VER >= 1600 + #define BEAST_COMPILER_SUPPORTS_NULLPTR 1 + #define BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 +#endif + +#if defined (_MSC_VER) && _MSC_VER >= 1700 + #define BEAST_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 +#endif + +//============================================================================== +// Declare some fake versions of nullptr and noexcept, for older compilers: +#if ! (DOXYGEN || BEAST_COMPILER_SUPPORTS_NOEXCEPT) + #ifdef noexcept + #undef noexcept + #endif + #define noexcept throw() + #if defined (_MSC_VER) && _MSC_VER > 1600 + #define _ALLOW_KEYWORD_MACROS 1 // (to stop VC2012 complaining) + #endif +#endif + +#if ! (DOXYGEN || BEAST_COMPILER_SUPPORTS_NULLPTR) + #ifdef nullptr + #undef nullptr + #endif + #define nullptr (0) +#endif + +#if ! (DOXYGEN || BEAST_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) + #undef override + #define override +#endif + +#endif // BEAST_PLATFORMDEFS_BEASTHEADER diff --git a/modules/beast_core/system/beast_StandardHeader.h b/modules/beast_core/system/beast_StandardHeader.h new file mode 100644 index 0000000000..ce4e4f7efe --- /dev/null +++ b/modules/beast_core/system/beast_StandardHeader.h @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_STANDARDHEADER_BEASTHEADER +#define BEAST_STANDARDHEADER_BEASTHEADER + +//============================================================================== +/** Current BEAST version number. + + See also SystemStats::getBEASTVersion() for a string version. +*/ +#define BEAST_MAJOR_VERSION 0 +#define BEAST_MINOR_VERSION 0 +#define BEAST_BUILDNUMBER 1 + +/** Current Beast version number. + + Bits 16 to 32 = major version. + Bits 8 to 16 = minor version. + Bits 0 to 8 = point release. + + See also SystemStats::getBEASTVersion() for a string version. +*/ +#define BEAST_VERSION ((BEAST_MAJOR_VERSION << 16) + (BEAST_MINOR_VERSION << 8) + BEAST_BUILDNUMBER) + + +//============================================================================== +#include "beast_TargetPlatform.h" // (sets up the various BEAST_WINDOWS, BEAST_MAC, etc flags) +#include "beast_PlatformDefs.h" + +//============================================================================== +// Now we'll include some common OS headers.. +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4245 4100) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if BEAST_USE_INTRINSICS + #include +#endif + +#if BEAST_MAC || BEAST_IOS + #include +#endif + +#if BEAST_LINUX + #include + + #if __INTEL_COMPILER + #if __ia64__ + #include + #else + #include + #endif + #endif +#endif + +#if BEAST_MSVC && BEAST_DEBUG + #include +#endif + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +#if BEAST_ANDROID + #include + #include +#endif + +// undef symbols that are sometimes set by misguided 3rd-party headers.. +#undef check +#undef TYPE_BOOL +#undef max +#undef min + +//============================================================================== +// DLL building settings on Windows +#if BEAST_MSVC + #ifdef BEAST_DLL_BUILD + #define BEAST_API __declspec (dllexport) + #pragma warning (disable: 4251) + #elif defined (BEAST_DLL) + #define BEAST_API __declspec (dllimport) + #pragma warning (disable: 4251) + #endif + #ifdef __INTEL_COMPILER + #pragma warning (disable: 1125) // (virtual override warning) + #endif +#elif defined (BEAST_DLL) || defined (BEAST_DLL_BUILD) + #define BEAST_API __attribute__ ((visibility("default"))) +#endif + +//============================================================================== +#ifndef BEAST_API + #define BEAST_API /**< This macro is added to all beast public class declarations. */ +#endif + +#if BEAST_MSVC && BEAST_DLL_BUILD + #define BEAST_PUBLIC_IN_DLL_BUILD(declaration) public: declaration; private: +#else + #define BEAST_PUBLIC_IN_DLL_BUILD(declaration) declaration; +#endif + +/** This macro is added to all beast public function declarations. */ +#define BEAST_PUBLIC_FUNCTION BEAST_API BEAST_CALLTYPE + +#if (! defined (BEAST_CATCH_DEPRECATED_CODE_MISUSE)) && BEAST_DEBUG && ! DOXYGEN + /** This turns on some non-essential bits of code that should prevent old code from compiling + in cases where method signatures have changed, etc. + */ + #define BEAST_CATCH_DEPRECATED_CODE_MISUSE 1 +#endif + +#ifndef DOXYGEN + #define BEAST_NAMESPACE beast // This old macro is deprecated: you should just use the beast namespace directly. +#endif + +//============================================================================== +// Now include some common headers... +namespace beast +{ + extern BEAST_API bool BEAST_CALLTYPE beast_isRunningUnderDebugger(); + extern BEAST_API void BEAST_CALLTYPE logAssertion (const char* file, int line) noexcept; + + #include "../memory/beast_Memory.h" + #include "../maths/beast_MathsFunctions.h" + #include "../memory/beast_ByteOrder.h" + #include "../logging/beast_Logger.h" + #include "../memory/beast_LeakedObjectDetector.h" +} + +#endif // BEAST_STANDARDHEADER_BEASTHEADER diff --git a/modules/beast_core/system/beast_SystemStats.cpp b/modules/beast_core/system/beast_SystemStats.cpp new file mode 100644 index 0000000000..24f361ecb8 --- /dev/null +++ b/modules/beast_core/system/beast_SystemStats.cpp @@ -0,0 +1,152 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +const SystemStats::CPUFlags& SystemStats::getCPUFlags() +{ + static CPUFlags cpuFlags; + return cpuFlags; +} + +String SystemStats::getBEASTVersion() +{ + // Some basic tests, to keep an eye on things and make sure these types work ok + // on all platforms. Let me know if any of these assertions fail on your system! + static_bassert (sizeof (pointer_sized_int) == sizeof (void*)); + static_bassert (sizeof (int8) == 1); + static_bassert (sizeof (uint8) == 1); + static_bassert (sizeof (int16) == 2); + static_bassert (sizeof (uint16) == 2); + static_bassert (sizeof (int32) == 4); + static_bassert (sizeof (uint32) == 4); + static_bassert (sizeof (int64) == 8); + static_bassert (sizeof (uint64) == 8); + + return "BEAST v" BEAST_STRINGIFY(BEAST_MAJOR_VERSION) + "." BEAST_STRINGIFY(BEAST_MINOR_VERSION) + "." BEAST_STRINGIFY(BEAST_BUILDNUMBER); +} + +#if BEAST_ANDROID && ! defined (BEAST_DISABLE_BEAST_VERSION_PRINTING) + #define BEAST_DISABLE_BEAST_VERSION_PRINTING 1 +#endif + +#if BEAST_DEBUG && ! BEAST_DISABLE_BEAST_VERSION_PRINTING + struct BeastVersionPrinter + { + BeastVersionPrinter() + { + DBG (SystemStats::getBEASTVersion()); + } + }; + + static BeastVersionPrinter beastVersionPrinter; +#endif + + +//============================================================================== +String SystemStats::getStackBacktrace() +{ + String result; + + #if BEAST_ANDROID || BEAST_MINGW + jassertfalse; // sorry, not implemented yet! + + #elif BEAST_WINDOWS + HANDLE process = GetCurrentProcess(); + SymInitialize (process, nullptr, TRUE); + + void* stack[128]; + int frames = (int) CaptureStackBackTrace (0, numElementsInArray (stack), stack, nullptr); + + HeapBlock symbol; + symbol.calloc (sizeof(SYMBOL_INFO) + 256, 1); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof (SYMBOL_INFO); + + for (int i = 0; i < frames; ++i) + { + DWORD64 displacement = 0; + + if (SymFromAddr (process, (DWORD64) stack[i], &displacement, symbol)) + { + result << i << ": "; + + IMAGEHLP_MODULE64 moduleInfo; + zerostruct (moduleInfo); + moduleInfo.SizeOfStruct = sizeof (moduleInfo); + + if (::SymGetModuleInfo64 (process, symbol->ModBase, &moduleInfo)) + result << moduleInfo.ModuleName << ": "; + + result << symbol->Name << " + 0x" << String::toHexString ((int64) displacement) << newLine; + } + } + + #else + void* stack[128]; + int frames = backtrace (stack, numElementsInArray (stack)); + char** frameStrings = backtrace_symbols (stack, frames); + + for (int i = 0; i < frames; ++i) + result << frameStrings[i] << newLine; + + ::free (frameStrings); + #endif + + return result; +} + +//============================================================================== +static SystemStats::CrashHandlerFunction globalCrashHandler = nullptr; + +#if BEAST_WINDOWS +static LONG WINAPI handleCrash (LPEXCEPTION_POINTERS) +{ + globalCrashHandler(); + return EXCEPTION_EXECUTE_HANDLER; +} +#else +static void handleCrash (int) +{ + globalCrashHandler(); + kill (getpid(), SIGKILL); +} +#endif + +void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler) +{ + bassert (handler != nullptr); // This must be a valid function. + globalCrashHandler = handler; + + #if BEAST_WINDOWS + SetUnhandledExceptionFilter (handleCrash); + #else + const int signals[] = { SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGSYS }; + + for (int i = 0; i < numElementsInArray (signals); ++i) + { + ::signal (signals[i], handleCrash); + ::siginterrupt (signals[i], 1); + } + #endif +} diff --git a/modules/beast_core/system/beast_SystemStats.h b/modules/beast_core/system/beast_SystemStats.h new file mode 100644 index 0000000000..761f7c91bf --- /dev/null +++ b/modules/beast_core/system/beast_SystemStats.h @@ -0,0 +1,201 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SYSTEMSTATS_BEASTHEADER +#define BEAST_SYSTEMSTATS_BEASTHEADER + +#include "../text/beast_StringArray.h" + + +//============================================================================== +/** + Contains methods for finding out about the current hardware and OS configuration. +*/ +class BEAST_API SystemStats +{ +public: + //============================================================================== + /** Returns the current version of BEAST, + See also the BEAST_VERSION, BEAST_MAJOR_VERSION and BEAST_MINOR_VERSION macros. + */ + static String getBEASTVersion(); + + //============================================================================== + /** The set of possible results of the getOperatingSystemType() method. */ + enum OperatingSystemType + { + UnknownOS = 0, + + Linux = 0x2000, + Android = 0x3000, + iOS = 0x8000, + + MacOSX_10_4 = 0x1004, + MacOSX_10_5 = 0x1005, + MacOSX_10_6 = 0x1006, + MacOSX_10_7 = 0x1007, + MacOSX_10_8 = 0x1008, + + Win2000 = 0x4105, + WinXP = 0x4106, + WinVista = 0x4107, + Windows7 = 0x4108, + Windows8 = 0x4109, + + Windows = 0x4000, /**< To test whether any version of Windows is running, + you can use the expression ((getOperatingSystemType() & Windows) != 0). */ + }; + + /** Returns the type of operating system we're running on. + + @returns one of the values from the OperatingSystemType enum. + @see getOperatingSystemName + */ + static OperatingSystemType getOperatingSystemType(); + + /** Returns the name of the type of operating system we're running on. + + @returns a string describing the OS type. + @see getOperatingSystemType + */ + static String getOperatingSystemName(); + + /** Returns true if the OS is 64-bit, or false for a 32-bit OS. + */ + static bool isOperatingSystem64Bit(); + + /** Returns an environment variable. + If the named value isn't set, this will return the defaultValue string instead. + */ + static String getEnvironmentVariable (const String& name, const String& defaultValue); + + //============================================================================== + /** Returns the current user's name, if available. + @see getFullUserName() + */ + static String getLogonName(); + + /** Returns the current user's full name, if available. + On some OSes, this may just return the same value as getLogonName(). + @see getLogonName() + */ + static String getFullUserName(); + + /** Returns the host-name of the computer. */ + static String getComputerName(); + + /** Returns the language of the user's locale. + The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2) + */ + static String getUserLanguage(); + + /** Returns the region of the user's locale. + The return value is a 2 letter country code (ISO 3166-1 alpha-2). + */ + static String getUserRegion(); + + /** Returns the user's display language. + The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2) + */ + static String getDisplayLanguage(); + + //============================================================================== + // CPU and memory information.. + + /** Returns the number of CPUs. */ + static int getNumCpus() noexcept { return getCPUFlags().numCpus; } + + /** Returns the approximate CPU speed. + @returns the speed in megahertz, e.g. 1500, 2500, 32000 (depending on + what year you're reading this...) + */ + static int getCpuSpeedInMegaherz(); + + /** Returns a string to indicate the CPU vendor. + Might not be known on some systems. + */ + static String getCpuVendor(); + + /** Checks whether Intel MMX instructions are available. */ + static bool hasMMX() noexcept { return getCPUFlags().hasMMX; } + + /** Checks whether Intel SSE instructions are available. */ + static bool hasSSE() noexcept { return getCPUFlags().hasSSE; } + + /** Checks whether Intel SSE2 instructions are available. */ + static bool hasSSE2() noexcept { return getCPUFlags().hasSSE2; } + + /** Checks whether AMD 3DNOW instructions are available. */ + static bool has3DNow() noexcept { return getCPUFlags().has3DNow; } + + //============================================================================== + /** Finds out how much RAM is in the machine. + @returns the approximate number of megabytes of memory, or zero if + something goes wrong when finding out. + */ + static int getMemorySizeInMegabytes(); + + /** Returns the system page-size. + This is only used by programmers with beards. + */ + static int getPageSize(); + + //============================================================================== + /** Returns a backtrace of the current call-stack. + The usefulness of the result will depend on the level of debug symbols + that are available in the executable. + */ + static String getStackBacktrace(); + + /** A void() function type, used by setApplicationCrashHandler(). */ + typedef void (*CrashHandlerFunction)(); + + /** Sets up a global callback function that will be called if the application + executes some kind of illegal instruction. + + You may want to call getStackBacktrace() in your handler function, to find out + where the problem happened and log it, etc. + */ + static void setApplicationCrashHandler (CrashHandlerFunction); + +private: + //============================================================================== + struct CPUFlags + { + CPUFlags(); + + int numCpus; + bool hasMMX : 1; + bool hasSSE : 1; + bool hasSSE2 : 1; + bool has3DNow : 1; + }; + + SystemStats(); + static const CPUFlags& getCPUFlags(); + + BEAST_DECLARE_NON_COPYABLE (SystemStats) +}; + + +#endif // BEAST_SYSTEMSTATS_BEASTHEADER diff --git a/modules/beast_core/system/beast_TargetPlatform.h b/modules/beast_core/system/beast_TargetPlatform.h new file mode 100644 index 0000000000..91b5df6cc9 --- /dev/null +++ b/modules/beast_core/system/beast_TargetPlatform.h @@ -0,0 +1,194 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_TARGETPLATFORM_BEASTHEADER +#define BEAST_TARGETPLATFORM_BEASTHEADER + +//============================================================================== +/* This file figures out which platform is being built, and defines some macros + that the rest of the code can use for OS-specific compilation. + + Macros that will be set here are: + + - One of BEAST_WINDOWS, BEAST_MAC BEAST_LINUX, BEAST_IOS, BEAST_ANDROID, etc. + - Either BEAST_32BIT or BEAST_64BIT, depending on the architecture. + - Either BEAST_LITTLE_ENDIAN or BEAST_BIG_ENDIAN. + - Either BEAST_INTEL or BEAST_PPC + - Either BEAST_GCC or BEAST_MSVC +*/ + +//============================================================================== +#if (defined (_WIN32) || defined (_WIN64)) + #define BEAST_WIN32 1 + #define BEAST_WINDOWS 1 +#elif defined (BEAST_ANDROID) + #undef BEAST_ANDROID + #define BEAST_ANDROID 1 +#elif defined (LINUX) || defined (__linux__) + #define BEAST_LINUX 1 +#elif defined (__APPLE_CPP__) || defined(__APPLE_CC__) + #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) + #define Component CarbonDummyCompName + #include // (needed to find out what platform we're using) + #undef Point + #undef Component + + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #define BEAST_IPHONE 1 + #define BEAST_IOS 1 + #else + #define BEAST_MAC 1 + #endif +#else + #error "Unknown platform!" +#endif + +//============================================================================== +#if BEAST_WINDOWS + #ifdef _MSC_VER + #ifdef _WIN64 + #define BEAST_64BIT 1 + #else + #define BEAST_32BIT 1 + #endif + #endif + + #ifdef _DEBUG + #define BEAST_DEBUG 1 + #endif + + #ifdef __MINGW32__ + #define BEAST_MINGW 1 + #ifdef __MINGW64__ + #define BEAST_64BIT 1 + #else + #define BEAST_32BIT 1 + #endif + #endif + + /** If defined, this indicates that the processor is little-endian. */ + #define BEAST_LITTLE_ENDIAN 1 + + #define BEAST_INTEL 1 +#endif + +//============================================================================== +#if BEAST_MAC || BEAST_IOS + + #if defined (DEBUG) || defined (_DEBUG) || ! (defined (NDEBUG) || defined (_NDEBUG)) + #define BEAST_DEBUG 1 + #endif + + #if ! (defined (DEBUG) || defined (_DEBUG) || defined (NDEBUG) || defined (_NDEBUG)) + #warning "Neither NDEBUG or DEBUG has been defined - you should set one of these to make it clear whether this is a release build," + #endif + + #ifdef __LITTLE_ENDIAN__ + #define BEAST_LITTLE_ENDIAN 1 + #else + #define BEAST_BIG_ENDIAN 1 + #endif +#endif + +#if BEAST_MAC + + #if defined (__ppc__) || defined (__ppc64__) + #define BEAST_PPC 1 + #else + #define BEAST_INTEL 1 + #endif + + #ifdef __LP64__ + #define BEAST_64BIT 1 + #else + #define BEAST_32BIT 1 + #endif + + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4 + #error "Building for OSX 10.3 is no longer supported!" + #endif + + #ifndef MAC_OS_X_VERSION_10_5 + #error "To build with 10.4 compatibility, use a 10.5 or 10.6 SDK and set the deployment target to 10.4" + #endif + +#endif + +//============================================================================== +#if BEAST_LINUX || BEAST_ANDROID + + #ifdef _DEBUG + #define BEAST_DEBUG 1 + #endif + + // Allow override for big-endian Linux platforms + #if defined (__LITTLE_ENDIAN__) || ! defined (BEAST_BIG_ENDIAN) + #define BEAST_LITTLE_ENDIAN 1 + #undef BEAST_BIG_ENDIAN + #else + #undef BEAST_LITTLE_ENDIAN + #define BEAST_BIG_ENDIAN 1 + #endif + + #if defined (__LP64__) || defined (_LP64) + #define BEAST_64BIT 1 + #else + #define BEAST_32BIT 1 + #endif + + #if __MMX__ || __SSE__ || __amd64__ + #define BEAST_INTEL 1 + #endif +#endif + +//============================================================================== +// Compiler type macros. + +#ifdef __clang__ + #define BEAST_CLANG 1 + #define BEAST_GCC 1 +#elif defined (__GNUC__) + #define BEAST_GCC 1 +#elif defined (_MSC_VER) + #define BEAST_MSVC 1 + + #if _MSC_VER < 1500 + #define BEAST_VC8_OR_EARLIER 1 + + #if _MSC_VER < 1400 + #define BEAST_VC7_OR_EARLIER 1 + + #if _MSC_VER < 1300 + #warning "MSVC 6.0 is no longer supported!" + #endif + #endif + #endif + + #if BEAST_64BIT || ! BEAST_VC7_OR_EARLIER + #define BEAST_USE_INTRINSICS 1 + #endif +#else + #error unknown compiler +#endif + +#endif // BEAST_TARGETPLATFORM_BEASTHEADER diff --git a/modules/beast_core/text/beast_CharPointer_ASCII.h b/modules/beast_core/text/beast_CharPointer_ASCII.h new file mode 100644 index 0000000000..8b09445ba4 --- /dev/null +++ b/modules/beast_core/text/beast_CharPointer_ASCII.h @@ -0,0 +1,382 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CHARPOINTER_ASCII_BEASTHEADER +#define BEAST_CHARPOINTER_ASCII_BEASTHEADER + + +//============================================================================== +/** + Wraps a pointer to a null-terminated ASCII character string, and provides + various methods to operate on the data. + + A valid ASCII string is assumed to not contain any characters above 127. + + @see CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32 +*/ +class CharPointer_ASCII +{ +public: + typedef char CharType; + + inline explicit CharPointer_ASCII (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_ASCII (const CharPointer_ASCII& other) noexcept + : data (other.data) + { + } + + inline CharPointer_ASCII operator= (const CharPointer_ASCII other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_ASCII operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_ASCII other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_ASCII other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_ASCII other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_ASCII other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_ASCII other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_ASCII other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + inline beast_wchar operator*() const noexcept { return (beast_wchar) (uint8) *data; } + + /** Moves this pointer along to the next character in the string. */ + inline CharPointer_ASCII operator++() noexcept + { + ++data; + return *this; + } + + /** Moves this pointer to the previous character in the string. */ + inline CharPointer_ASCII operator--() noexcept + { + --data; + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + inline beast_wchar getAndAdvance() noexcept { return (beast_wchar) (uint8) *data++; } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_ASCII operator++ (int) noexcept + { + CharPointer_ASCII temp (*this); + ++data; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + inline void operator+= (const int numToSkip) noexcept + { + data += numToSkip; + } + + inline void operator-= (const int numToSkip) noexcept + { + data -= numToSkip; + } + + /** Returns the character at a given character index from the start of the string. */ + inline beast_wchar operator[] (const int characterIndex) const noexcept + { + return (beast_wchar) (unsigned char) data [characterIndex]; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_ASCII operator+ (const int numToSkip) const noexcept + { + return CharPointer_ASCII (data + numToSkip); + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_ASCII operator- (const int numToSkip) const noexcept + { + return CharPointer_ASCII (data - numToSkip); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + inline void write (const beast_wchar charToWrite) noexcept + { + *data++ = (char) charToWrite; + } + + inline void replaceChar (const beast_wchar newChar) noexcept + { + *data = (char) newChar; + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + return (size_t) strlen (data); + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_ASCII end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + return length() + 1; + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static inline size_t getBytesRequiredFor (const beast_wchar) noexcept + { + return 1; + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (const CharPointer text) noexcept + { + return text.length(); + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_ASCII findTerminatingNull() const noexcept + { + return CharPointer_ASCII (data + length()); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_ASCII src) noexcept + { + strcpy (data, src.data); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one. */ + int compare (const CharPointer_ASCII other) const noexcept + { + return strcmp (data, other.data); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one, up to a specified number of characters. */ + int compareUpTo (const CharPointer_ASCII other, const int maxChars) const noexcept + { + return strncmp (data, other.data, (size_t) maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + int compareIgnoreCase (const CharPointer_ASCII other) const + { + #if BEAST_WINDOWS + return stricmp (data, other.data); + #else + return strcasecmp (data, other.data); + #endif + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind) const noexcept + { + int i = 0; + + while (data[i] != 0) + { + if (data[i] == (char) charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const { return CharacterFunctions::isWhitespace (*data) != 0; } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const { return CharacterFunctions::isDigit (*data) != 0; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const { return CharacterFunctions::isLetter (*data) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const { return CharacterFunctions::isLetterOrDigit (*data) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const { return CharacterFunctions::isUpperCase ((beast_wchar) (uint8) *data) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const { return CharacterFunctions::isLowerCase ((beast_wchar) (uint8) *data) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + beast_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase ((beast_wchar) (uint8) *data); } + /** Returns a lower-case version of the first character of this string. */ + beast_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase ((beast_wchar) (uint8) *data); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return atoi (data); } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if BEAST_LINUX || BEAST_ANDROID + return atoll (data); + #elif BEAST_WINDOWS + return _atoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_ASCII findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (beast_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 128; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + while (--maxBytesToRead >= 0) + { + if (((signed char) *dataToTest) <= 0) + return *dataToTest == 0; + + ++dataToTest; + } + + return true; + } + +private: + CharType* data; +}; + + +#endif // BEAST_CHARPOINTER_ASCII_BEASTHEADER diff --git a/modules/beast_core/text/beast_CharPointer_UTF16.h b/modules/beast_core/text/beast_CharPointer_UTF16.h new file mode 100644 index 0000000000..22a0bdb213 --- /dev/null +++ b/modules/beast_core/text/beast_CharPointer_UTF16.h @@ -0,0 +1,496 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CHARPOINTER_UTF16_BEASTHEADER +#define BEAST_CHARPOINTER_UTF16_BEASTHEADER + + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-16 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF8, CharPointer_UTF32 +*/ +class CharPointer_UTF16 +{ +public: + #if BEAST_NATIVE_WCHAR_IS_UTF16 + typedef wchar_t CharType; + #else + typedef int16 CharType; + #endif + + inline explicit CharPointer_UTF16 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF16 (const CharPointer_UTF16& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF16 operator= (CharPointer_UTF16 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF16 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF16 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF16 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF16 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF16 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF16 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF16 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + beast_wchar operator*() const noexcept + { + uint32 n = (uint32) (uint16) *data; + + if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) data[1]) >= 0xdc00) + n = 0x10000 + (((n - 0xd800) << 10) | (((uint32) (uint16) data[1]) - 0xdc00)); + + return (beast_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF16 operator++() noexcept + { + const beast_wchar n = *data++; + + if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) *data) >= 0xdc00) + ++data; + + return *this; + } + + /** Moves this pointer back to the previous character in the string. */ + CharPointer_UTF16 operator--() noexcept + { + const beast_wchar n = *--data; + + if (n >= 0xdc00 && n <= 0xdfff) + --data; + + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + beast_wchar getAndAdvance() noexcept + { + uint32 n = (uint32) (uint16) *data++; + + if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) *data) >= 0xdc00) + n = 0x10000 + ((((n - 0xd800) << 10) | (((uint32) (uint16) *data++) - 0xdc00))); + + return (beast_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF16 operator++ (int) noexcept + { + CharPointer_UTF16 temp (*this); + ++*this; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + void operator+= (int numToSkip) noexcept + { + if (numToSkip < 0) + { + while (++numToSkip <= 0) + --*this; + } + else + { + while (--numToSkip >= 0) + ++*this; + } + } + + /** Moves this pointer backwards by the specified number of characters. */ + void operator-= (int numToSkip) noexcept + { + operator+= (-numToSkip); + } + + /** Returns the character at a given character index from the start of the string. */ + beast_wchar operator[] (const int characterIndex) const noexcept + { + CharPointer_UTF16 p (*this); + p += characterIndex; + return *p; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF16 operator+ (const int numToSkip) const noexcept + { + CharPointer_UTF16 p (*this); + p += numToSkip; + return p; + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF16 operator- (const int numToSkip) const noexcept + { + CharPointer_UTF16 p (*this); + p += -numToSkip; + return p; + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + void write (beast_wchar charToWrite) noexcept + { + if (charToWrite >= 0x10000) + { + charToWrite -= 0x10000; + *data++ = (CharType) (0xd800 + (charToWrite >> 10)); + *data++ = (CharType) (0xdc00 + (charToWrite & 0x3ff)); + } + else + { + *data++ = (CharType) charToWrite; + } + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + const CharType* d = data; + size_t count = 0; + + for (;;) + { + const int n = *d++; + + if (n >= 0xd800 && n <= 0xdfff) + { + if (*d++ == 0) + break; + } + else if (n == 0) + break; + + ++count; + } + + return count; + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF16 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + return sizeof (CharType) * (findNullIndex (data) + 1); + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static size_t getBytesRequiredFor (const beast_wchar charToWrite) noexcept + { + return (charToWrite >= 0x10000) ? (sizeof (CharType) * 2) : sizeof (CharType); + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (CharPointer text) noexcept + { + size_t count = 0; + beast_wchar n; + + while ((n = text.getAndAdvance()) != 0) + count += getBytesRequiredFor (n); + + return count; + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF16 findTerminatingNull() const noexcept + { + const CharType* t = data; + + while (*t != 0) + ++t; + + return CharPointer_UTF16 (t); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF16 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const noexcept + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + #if BEAST_WINDOWS && ! DOXYGEN + int compareIgnoreCase (const CharPointer_UTF16 other) const noexcept + { + return _wcsicmp (data, other.data); + } + + int compareIgnoreCaseUpTo (const CharPointer_UTF16 other, int maxChars) const noexcept + { + return _wcsnicmp (data, other.data, (size_t) maxChars); + } + + int indexOf (const CharPointer_UTF16 stringToFind) const noexcept + { + const CharType* const t = wcsstr (data, stringToFind.getAddress()); + return t == nullptr ? -1 : (int) (t - data); + } + #endif + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind) const noexcept + { + return CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const noexcept { return CharacterFunctions::isWhitespace (operator*()) != 0; } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const noexcept { return CharacterFunctions::isDigit (operator*()) != 0; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + beast_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + beast_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept + { + #if BEAST_WINDOWS + return _wtoi (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if BEAST_WINDOWS + return _wtoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF16 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (beast_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff + && (((unsigned int) character) < 0xd800 || ((unsigned int) character) > 0xdfff); + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + maxBytesToRead /= sizeof (CharType); + + while (--maxBytesToRead >= 0 && *dataToTest != 0) + { + const uint32 n = (uint32) (uint16) *dataToTest++; + + if (n >= 0xd800) + { + if (n > 0x10ffff) + return false; + + if (n <= 0xdfff) + { + if (n > 0xdc00) + return false; + + const uint32 nextChar = (uint32) (uint16) *dataToTest++; + + if (nextChar < 0xdc00 || nextChar > 0xdfff) + return false; + } + } + } + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF16 atomicSwap (const CharPointer_UTF16 newValue) + { + return CharPointer_UTF16 (reinterpret_cast &> (data).exchange (newValue.data)); + } + + /** These values are the byte-order-mark (BOM) values for a UTF-16 stream. */ + enum + { + byteOrderMarkBE1 = 0xfe, + byteOrderMarkBE2 = 0xff, + byteOrderMarkLE1 = 0xff, + byteOrderMarkLE2 = 0xfe + }; + +private: + CharType* data; + + static unsigned int findNullIndex (const CharType* const t) noexcept + { + unsigned int n = 0; + + while (t[n] != 0) + ++n; + + return n; + } +}; + + +#endif // BEAST_CHARPOINTER_UTF16_BEASTHEADER diff --git a/modules/beast_core/text/beast_CharPointer_UTF32.h b/modules/beast_core/text/beast_CharPointer_UTF32.h new file mode 100644 index 0000000000..fe24978e18 --- /dev/null +++ b/modules/beast_core/text/beast_CharPointer_UTF32.h @@ -0,0 +1,373 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CHARPOINTER_UTF32_BEASTHEADER +#define BEAST_CHARPOINTER_UTF32_BEASTHEADER + + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-32 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF8, CharPointer_UTF16 +*/ +class CharPointer_UTF32 +{ +public: + typedef beast_wchar CharType; + + inline explicit CharPointer_UTF32 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF32 (const CharPointer_UTF32& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF32 operator= (CharPointer_UTF32 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF32 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF32 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF32 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF32 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF32 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF32 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF32 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + inline beast_wchar operator*() const noexcept { return *data; } + + /** Moves this pointer along to the next character in the string. */ + inline CharPointer_UTF32 operator++() noexcept + { + ++data; + return *this; + } + + /** Moves this pointer to the previous character in the string. */ + inline CharPointer_UTF32 operator--() noexcept + { + --data; + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + inline beast_wchar getAndAdvance() noexcept { return *data++; } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF32 operator++ (int) noexcept + { + CharPointer_UTF32 temp (*this); + ++data; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + inline void operator+= (const int numToSkip) noexcept + { + data += numToSkip; + } + + inline void operator-= (const int numToSkip) noexcept + { + data -= numToSkip; + } + + /** Returns the character at a given character index from the start of the string. */ + inline beast_wchar& operator[] (const int characterIndex) const noexcept + { + return data [characterIndex]; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF32 operator+ (const int numToSkip) const noexcept + { + return CharPointer_UTF32 (data + numToSkip); + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF32 operator- (const int numToSkip) const noexcept + { + return CharPointer_UTF32 (data - numToSkip); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + inline void write (const beast_wchar charToWrite) noexcept + { + *data++ = charToWrite; + } + + inline void replaceChar (const beast_wchar newChar) noexcept + { + *data = newChar; + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + #if BEAST_NATIVE_WCHAR_IS_UTF32 && ! BEAST_ANDROID + return wcslen (data); + #else + size_t n = 0; + while (data[n] != 0) + ++n; + return n; + #endif + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF32 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + return sizeof (CharType) * (length() + 1); + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static inline size_t getBytesRequiredFor (const beast_wchar) noexcept + { + return sizeof (CharType); + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (const CharPointer text) noexcept + { + return sizeof (CharType) * text.length(); + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF32 findTerminatingNull() const noexcept + { + return CharPointer_UTF32 (data + length()); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF32 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + #if BEAST_NATIVE_WCHAR_IS_UTF32 && ! BEAST_ANDROID + /** Compares this string with another one. */ + int compare (const CharPointer_UTF32 other) const noexcept + { + return wcscmp (data, other.data); + } + #endif + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind) const noexcept + { + int i = 0; + + while (data[i] != 0) + { + if (data[i] == charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const { return CharacterFunctions::isWhitespace (*data) != 0; } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const { return CharacterFunctions::isDigit (*data) != 0; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const { return CharacterFunctions::isLetter (*data) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const { return CharacterFunctions::isLetterOrDigit (*data) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const { return CharacterFunctions::isUpperCase (*data) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const { return CharacterFunctions::isLowerCase (*data) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + beast_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (*data); } + /** Returns a lower-case version of the first character of this string. */ + beast_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (*data); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return CharacterFunctions::getIntValue (*this); } + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept { return CharacterFunctions::getIntValue (*this); } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF32 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (beast_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + maxBytesToRead /= sizeof (CharType); + + while (--maxBytesToRead >= 0 && *dataToTest != 0) + if (! canRepresent (*dataToTest++)) + return false; + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF32 atomicSwap (const CharPointer_UTF32 newValue) + { + return CharPointer_UTF32 (reinterpret_cast &> (data).exchange (newValue.data)); + } + +private: + CharType* data; +}; + + +#endif // BEAST_CHARPOINTER_UTF32_BEASTHEADER diff --git a/modules/beast_core/text/beast_CharPointer_UTF8.h b/modules/beast_core/text/beast_CharPointer_UTF8.h new file mode 100644 index 0000000000..0b4720bd09 --- /dev/null +++ b/modules/beast_core/text/beast_CharPointer_UTF8.h @@ -0,0 +1,560 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CHARPOINTER_UTF8_BEASTHEADER +#define BEAST_CHARPOINTER_UTF8_BEASTHEADER + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-8 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF16, CharPointer_UTF32 +*/ +class CharPointer_UTF8 +{ +public: + typedef char CharType; + + inline explicit CharPointer_UTF8 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF8 (const CharPointer_UTF8& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF8 operator= (CharPointer_UTF8 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF8 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF8 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF8 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF8 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF8 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF8 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF8 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + beast_wchar operator*() const noexcept + { + const signed char byte = (signed char) *data; + + if (byte >= 0) + return (beast_wchar) (uint8) byte; + + uint32 n = (uint32) (uint8) byte; + uint32 mask = 0x7f; + uint32 bit = 0x40; + size_t numExtraValues = 0; + + while ((n & bit) != 0 && bit > 0x10) + { + mask >>= 1; + ++numExtraValues; + bit >>= 1; + } + + n &= mask; + + for (size_t i = 1; i <= numExtraValues; ++i) + { + const uint8 nextByte = (uint8) data [i]; + + if ((nextByte & 0xc0) != 0x80) + break; + + n <<= 6; + n |= (nextByte & 0x3f); + } + + return (beast_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF8& operator++() noexcept + { + const signed char n = (signed char) *data++; + + if (n < 0) + { + beast_wchar bit = 0x40; + + while ((n & bit) != 0 && bit > 0x8) + { + ++data; + bit >>= 1; + } + } + + return *this; + } + + /** Moves this pointer back to the previous character in the string. */ + CharPointer_UTF8 operator--() noexcept + { + int count = 0; + + while ((*--data & 0xc0) == 0x80 && ++count < 4) + {} + + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + beast_wchar getAndAdvance() noexcept + { + const signed char byte = (signed char) *data++; + + if (byte >= 0) + return (beast_wchar) (uint8) byte; + + uint32 n = (uint32) (uint8) byte; + uint32 mask = 0x7f; + uint32 bit = 0x40; + int numExtraValues = 0; + + while ((n & bit) != 0 && bit > 0x8) + { + mask >>= 1; + ++numExtraValues; + bit >>= 1; + } + + n &= mask; + + while (--numExtraValues >= 0) + { + const uint32 nextByte = (uint32) (uint8) *data++; + + if ((nextByte & 0xc0) != 0x80) + break; + + n <<= 6; + n |= (nextByte & 0x3f); + } + + return (beast_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF8 operator++ (int) noexcept + { + CharPointer_UTF8 temp (*this); + ++*this; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + void operator+= (int numToSkip) noexcept + { + if (numToSkip < 0) + { + while (++numToSkip <= 0) + --*this; + } + else + { + while (--numToSkip >= 0) + ++*this; + } + } + + /** Moves this pointer backwards by the specified number of characters. */ + void operator-= (int numToSkip) noexcept + { + operator+= (-numToSkip); + } + + /** Returns the character at a given character index from the start of the string. */ + beast_wchar operator[] (int characterIndex) const noexcept + { + CharPointer_UTF8 p (*this); + p += characterIndex; + return *p; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF8 operator+ (int numToSkip) const noexcept + { + CharPointer_UTF8 p (*this); + p += numToSkip; + return p; + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF8 operator- (int numToSkip) const noexcept + { + CharPointer_UTF8 p (*this); + p += -numToSkip; + return p; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + const CharType* d = data; + size_t count = 0; + + for (;;) + { + const uint32 n = (uint32) (uint8) *d++; + + if ((n & 0x80) != 0) + { + uint32 bit = 0x40; + + while ((n & bit) != 0) + { + ++d; + bit >>= 1; + + if (bit == 0) + break; // illegal utf-8 sequence + } + } + else if (n == 0) + break; + + ++count; + } + + return count; + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF8 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + bassert (data != nullptr); + return strlen (data) + 1; + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static size_t getBytesRequiredFor (const beast_wchar charToWrite) noexcept + { + size_t num = 1; + const uint32 c = (uint32) charToWrite; + + if (c >= 0x80) + { + ++num; + if (c >= 0x800) + { + ++num; + if (c >= 0x10000) + ++num; + } + } + + return num; + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (CharPointer text) noexcept + { + size_t count = 0; + beast_wchar n; + + while ((n = text.getAndAdvance()) != 0) + count += getBytesRequiredFor (n); + + return count; + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF8 findTerminatingNull() const noexcept + { + return CharPointer_UTF8 (data + strlen (data)); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + void write (const beast_wchar charToWrite) noexcept + { + const uint32 c = (uint32) charToWrite; + + if (c >= 0x80) + { + int numExtraBytes = 1; + if (c >= 0x800) + { + ++numExtraBytes; + if (c >= 0x10000) + ++numExtraBytes; + } + + *data++ = (CharType) ((uint32) (0xff << (7 - numExtraBytes)) | (c >> (numExtraBytes * 6))); + + while (--numExtraBytes >= 0) + *data++ = (CharType) (0x80 | (0x3f & (c >> (numExtraBytes * 6)))); + } + else + { + *data++ = (CharType) c; + } + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF8 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const noexcept + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one. */ + int compareIgnoreCase (const CharPointer_UTF8 other) const noexcept + { + #if BEAST_WINDOWS + return stricmp (data, other.data); + #else + return strcasecmp (data, other.data); + #endif + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind) const noexcept + { + return CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const beast_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const noexcept { return *data == ' ' || (*data <= 13 && *data >= 9); } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const noexcept { return *data >= '0' && *data <= '9'; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + beast_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + beast_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return atoi (data); } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if BEAST_LINUX || BEAST_ANDROID + return atoll (data); + #elif BEAST_WINDOWS + return _atoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF8 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (beast_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + while (--maxBytesToRead >= 0 && *dataToTest != 0) + { + const signed char byte = (signed char) *dataToTest++; + + if (byte < 0) + { + uint8 bit = 0x40; + int numExtraValues = 0; + + while ((byte & bit) != 0) + { + if (bit < 8) + return false; + + ++numExtraValues; + bit >>= 1; + + if (bit == 8 && (numExtraValues > maxBytesToRead + || *CharPointer_UTF8 (dataToTest - 1) > 0x10ffff)) + return false; + } + + maxBytesToRead -= numExtraValues; + if (maxBytesToRead < 0) + return false; + + while (--numExtraValues >= 0) + if ((*dataToTest++ & 0xc0) != 0x80) + return false; + } + } + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF8 atomicSwap (const CharPointer_UTF8 newValue) + { + return CharPointer_UTF8 (reinterpret_cast &> (data).exchange (newValue.data)); + } + + /** These values are the byte-order-mark (BOM) values for a UTF-8 stream. */ + enum + { + byteOrderMark1 = 0xef, + byteOrderMark2 = 0xbb, + byteOrderMark3 = 0xbf + }; + +private: + CharType* data; +}; + +#endif // BEAST_CHARPOINTER_UTF8_BEASTHEADER diff --git a/modules/beast_core/text/beast_CharacterFunctions.cpp b/modules/beast_core/text/beast_CharacterFunctions.cpp new file mode 100644 index 0000000000..0f70a0a9b8 --- /dev/null +++ b/modules/beast_core/text/beast_CharacterFunctions.cpp @@ -0,0 +1,149 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +//============================================================================== +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +beast_wchar CharacterFunctions::toUpperCase (const beast_wchar character) noexcept +{ + return towupper ((wchar_t) character); +} + +beast_wchar CharacterFunctions::toLowerCase (const beast_wchar character) noexcept +{ + return towlower ((wchar_t) character); +} + +bool CharacterFunctions::isUpperCase (const beast_wchar character) noexcept +{ + #if BEAST_WINDOWS + return iswupper ((wchar_t) character) != 0; + #else + return toLowerCase (character) != character; + #endif +} + +bool CharacterFunctions::isLowerCase (const beast_wchar character) noexcept +{ + #if BEAST_WINDOWS + return iswlower ((wchar_t) character) != 0; + #else + return toUpperCase (character) != character; + #endif +} + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +bool CharacterFunctions::isWhitespace (const char character) noexcept +{ + return character == ' ' || (character <= 13 && character >= 9); +} + +bool CharacterFunctions::isWhitespace (const beast_wchar character) noexcept +{ + return iswspace ((wchar_t) character) != 0; +} + +bool CharacterFunctions::isDigit (const char character) noexcept +{ + return (character >= '0' && character <= '9'); +} + +bool CharacterFunctions::isDigit (const beast_wchar character) noexcept +{ + return iswdigit ((wchar_t) character) != 0; +} + +bool CharacterFunctions::isLetter (const char character) noexcept +{ + return (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z'); +} + +bool CharacterFunctions::isLetter (const beast_wchar character) noexcept +{ + return iswalpha ((wchar_t) character) != 0; +} + +bool CharacterFunctions::isLetterOrDigit (const char character) noexcept +{ + return (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9'); +} + +bool CharacterFunctions::isLetterOrDigit (const beast_wchar character) noexcept +{ + return iswalnum ((wchar_t) character) != 0; +} + +int CharacterFunctions::getHexDigitValue (const beast_wchar digit) noexcept +{ + unsigned int d = (unsigned int) digit - '0'; + if (d < (unsigned int) 10) + return (int) d; + + d += (unsigned int) ('0' - 'a'); + if (d < (unsigned int) 6) + return (int) d + 10; + + d += (unsigned int) ('a' - 'A'); + if (d < (unsigned int) 6) + return (int) d + 10; + + return -1; +} + +double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept +{ + if (exponent == 0) + return value; + + if (value == 0) + return 0; + + const bool negative = (exponent < 0); + if (negative) + exponent = -exponent; + + double result = 1.0, power = 10.0; + for (int bit = 1; exponent != 0; bit <<= 1) + { + if ((exponent & bit) != 0) + { + exponent ^= bit; + result *= power; + if (exponent == 0) + break; + } + power *= power; + } + + return negative ? (value / result) : (value * result); +} diff --git a/modules/beast_core/text/beast_CharacterFunctions.h b/modules/beast_core/text/beast_CharacterFunctions.h new file mode 100644 index 0000000000..a8742a88b7 --- /dev/null +++ b/modules/beast_core/text/beast_CharacterFunctions.h @@ -0,0 +1,585 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CHARACTERFUNCTIONS_BEASTHEADER +#define BEAST_CHARACTERFUNCTIONS_BEASTHEADER + + +//============================================================================== +#if BEAST_WINDOWS && ! DOXYGEN + #define BEAST_NATIVE_WCHAR_IS_UTF8 0 + #define BEAST_NATIVE_WCHAR_IS_UTF16 1 + #define BEAST_NATIVE_WCHAR_IS_UTF32 0 +#else + /** This macro will be set to 1 if the compiler's native wchar_t is an 8-bit type. */ + #define BEAST_NATIVE_WCHAR_IS_UTF8 0 + /** This macro will be set to 1 if the compiler's native wchar_t is a 16-bit type. */ + #define BEAST_NATIVE_WCHAR_IS_UTF16 0 + /** This macro will be set to 1 if the compiler's native wchar_t is a 32-bit type. */ + #define BEAST_NATIVE_WCHAR_IS_UTF32 1 +#endif + +#if BEAST_NATIVE_WCHAR_IS_UTF32 || DOXYGEN + /** A platform-independent 32-bit unicode character type. */ + typedef wchar_t beast_wchar; +#else + typedef uint32 beast_wchar; +#endif + +#ifndef DOXYGEN + /** This macro is deprecated, but preserved for compatibility with old code. */ + #define BEAST_T(stringLiteral) (L##stringLiteral) +#endif + +#if BEAST_DEFINE_T_MACRO + /** The 'T' macro is an alternative for using the "L" prefix in front of a string literal. + + This macro is deprecated, but available for compatibility with old code if you set + BEAST_DEFINE_T_MACRO = 1. The fastest, most portable and best way to write your string + literals is as standard char strings, using escaped utf-8 character sequences for extended + characters, rather than trying to store them as wide-char strings. + */ + #define T(stringLiteral) BEAST_T(stringLiteral) +#endif + +//============================================================================== +/** + A collection of functions for manipulating characters and character strings. + + Most of these methods are designed for internal use by the String and CharPointer + classes, but some of them may be useful to call directly. + + @see String, CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32 +*/ +class BEAST_API CharacterFunctions +{ +public: + //============================================================================== + /** Converts a character to upper-case. */ + static beast_wchar toUpperCase (beast_wchar character) noexcept; + /** Converts a character to lower-case. */ + static beast_wchar toLowerCase (beast_wchar character) noexcept; + + /** Checks whether a unicode character is upper-case. */ + static bool isUpperCase (beast_wchar character) noexcept; + /** Checks whether a unicode character is lower-case. */ + static bool isLowerCase (beast_wchar character) noexcept; + + /** Checks whether a character is whitespace. */ + static bool isWhitespace (char character) noexcept; + /** Checks whether a character is whitespace. */ + static bool isWhitespace (beast_wchar character) noexcept; + + /** Checks whether a character is a digit. */ + static bool isDigit (char character) noexcept; + /** Checks whether a character is a digit. */ + static bool isDigit (beast_wchar character) noexcept; + + /** Checks whether a character is alphabetic. */ + static bool isLetter (char character) noexcept; + /** Checks whether a character is alphabetic. */ + static bool isLetter (beast_wchar character) noexcept; + + /** Checks whether a character is alphabetic or numeric. */ + static bool isLetterOrDigit (char character) noexcept; + /** Checks whether a character is alphabetic or numeric. */ + static bool isLetterOrDigit (beast_wchar character) noexcept; + + /** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ + static int getHexDigitValue (beast_wchar digit) noexcept; + + //============================================================================== + /** Parses a character string to read a floating-point number. + Note that this will advance the pointer that is passed in, leaving it at + the end of the number. + */ + template + static double readDoubleValue (CharPointerType& text) noexcept + { + double result[3] = { 0 }, accumulator[2] = { 0 }; + int exponentAdjustment[2] = { 0 }, exponentAccumulator[2] = { -1, -1 }; + int exponent = 0, decPointIndex = 0, digit = 0; + int lastDigit = 0, numSignificantDigits = 0; + bool isNegative = false, digitsFound = false; + const int maxSignificantDigits = 15 + 2; + + text = text.findEndOfWhitespace(); + beast_wchar c = *text; + + switch (c) + { + case '-': isNegative = true; // fall-through.. + case '+': c = *++text; + } + + switch (c) + { + case 'n': + case 'N': + if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N')) + return std::numeric_limits::quiet_NaN(); + break; + + case 'i': + case 'I': + if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F')) + return std::numeric_limits::infinity(); + break; + } + + for (;;) + { + if (text.isDigit()) + { + lastDigit = digit; + digit = (int) text.getAndAdvance() - '0'; + digitsFound = true; + + if (decPointIndex != 0) + exponentAdjustment[1]++; + + if (numSignificantDigits == 0 && digit == 0) + continue; + + if (++numSignificantDigits > maxSignificantDigits) + { + if (digit > 5) + ++accumulator [decPointIndex]; + else if (digit == 5 && (lastDigit & 1) != 0) + ++accumulator [decPointIndex]; + + if (decPointIndex > 0) + exponentAdjustment[1]--; + else + exponentAdjustment[0]++; + + while (text.isDigit()) + { + ++text; + if (decPointIndex == 0) + exponentAdjustment[0]++; + } + } + else + { + const double maxAccumulatorValue = (double) ((std::numeric_limits::max() - 9) / 10); + if (accumulator [decPointIndex] > maxAccumulatorValue) + { + result [decPointIndex] = mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex]) + + accumulator [decPointIndex]; + accumulator [decPointIndex] = 0; + exponentAccumulator [decPointIndex] = 0; + } + + accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit; + exponentAccumulator [decPointIndex]++; + } + } + else if (decPointIndex == 0 && *text == '.') + { + ++text; + decPointIndex = 1; + + if (numSignificantDigits > maxSignificantDigits) + { + while (text.isDigit()) + ++text; + break; + } + } + else + { + break; + } + } + + result[0] = mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0]; + + if (decPointIndex != 0) + result[1] = mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1]; + + c = *text; + if ((c == 'e' || c == 'E') && digitsFound) + { + bool negativeExponent = false; + + switch (*++text) + { + case '-': negativeExponent = true; // fall-through.. + case '+': ++text; + } + + while (text.isDigit()) + exponent = (exponent * 10) + ((int) text.getAndAdvance() - '0'); + + if (negativeExponent) + exponent = -exponent; + } + + double r = mulexp10 (result[0], exponent + exponentAdjustment[0]); + if (decPointIndex != 0) + r += mulexp10 (result[1], exponent - exponentAdjustment[1]); + + return isNegative ? -r : r; + } + + /** Parses a character string, to read a floating-point value. */ + template + static double getDoubleValue (CharPointerType text) noexcept + { + return readDoubleValue (text); + } + + //============================================================================== + /** Parses a character string, to read an integer value. */ + template + static IntType getIntValue (const CharPointerType text) noexcept + { + IntType v = 0; + CharPointerType s (text.findEndOfWhitespace()); + + const bool isNeg = *s == '-'; + if (isNeg) + ++s; + + for (;;) + { + const beast_wchar c = s.getAndAdvance(); + + if (c >= '0' && c <= '9') + v = v * 10 + (IntType) (c - '0'); + else + break; + } + + return isNeg ? -v : v; + } + + //============================================================================== + /** Counts the number of characters in a given string, stopping if the count exceeds + a specified limit. */ + template + static size_t lengthUpTo (CharPointerType text, const size_t maxCharsToCount) noexcept + { + size_t len = 0; + + while (len < maxCharsToCount && text.getAndAdvance() != 0) + ++len; + + return len; + } + + /** Counts the number of characters in a given string, stopping if the count exceeds + a specified end-pointer. */ + template + static size_t lengthUpTo (CharPointerType start, const CharPointerType end) noexcept + { + size_t len = 0; + + while (start < end && start.getAndAdvance() != 0) + ++len; + + return len; + } + + /** Copies null-terminated characters from one string to another. */ + template + static void copyAll (DestCharPointerType& dest, SrcCharPointerType src) noexcept + { + for (;;) + { + const beast_wchar c = src.getAndAdvance(); + + if (c == 0) + break; + + dest.write (c); + } + + dest.writeNull(); + } + + /** Copies characters from one string to another, up to a null terminator + or a given byte size limit. */ + template + static size_t copyWithDestByteLimit (DestCharPointerType& dest, SrcCharPointerType src, size_t maxBytesToWrite) noexcept + { + typename DestCharPointerType::CharType const* const startAddress = dest.getAddress(); + ssize_t maxBytes = (ssize_t) maxBytesToWrite; + maxBytes -= sizeof (typename DestCharPointerType::CharType); // (allow for a terminating null) + + for (;;) + { + const beast_wchar c = src.getAndAdvance(); + const size_t bytesNeeded = DestCharPointerType::getBytesRequiredFor (c); + + maxBytes -= bytesNeeded; + if (c == 0 || maxBytes < 0) + break; + + dest.write (c); + } + + dest.writeNull(); + + return (size_t) getAddressDifference (dest.getAddress(), startAddress) + + sizeof (typename DestCharPointerType::CharType); + } + + /** Copies characters from one string to another, up to a null terminator + or a given maximum number of characters. */ + template + static void copyWithCharLimit (DestCharPointerType& dest, SrcCharPointerType src, int maxChars) noexcept + { + while (--maxChars > 0) + { + const beast_wchar c = src.getAndAdvance(); + if (c == 0) + break; + + dest.write (c); + } + + dest.writeNull(); + } + + /** Compares two null-terminated character strings. */ + template + static int compare (CharPointerType1 s1, CharPointerType2 s2) noexcept + { + for (;;) + { + const int c1 = (int) s1.getAndAdvance(); + const int c2 = (int) s2.getAndAdvance(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; + } + + /** Compares two null-terminated character strings, up to a given number of characters. */ + template + static int compareUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept + { + while (--maxChars >= 0) + { + const int c1 = (int) s1.getAndAdvance(); + const int c2 = (int) s2.getAndAdvance(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; + } + + /** Compares two null-terminated character strings, using a case-independant match. */ + template + static int compareIgnoreCase (CharPointerType1 s1, CharPointerType2 s2) noexcept + { + for (;;) + { + const int c1 = (int) s1.toUpperCase(); ++s1; + const int c2 = (int) s2.toUpperCase(); ++s2; + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; + } + + /** Compares two null-terminated character strings, using a case-independent match. */ + template + static int compareIgnoreCaseUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept + { + while (--maxChars >= 0) + { + const int c1 = (int) s1.toUpperCase(); ++s1; + const int c2 = (int) s2.toUpperCase(); ++s2; + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; + } + + /** Finds the character index of a given substring in another string. + Returns -1 if the substring is not found. + */ + template + static int indexOf (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept + { + int index = 0; + const int substringLength = (int) substringToLookFor.length(); + + for (;;) + { + if (textToSearch.compareUpTo (substringToLookFor, substringLength) == 0) + return index; + + if (textToSearch.getAndAdvance() == 0) + return -1; + + ++index; + } + } + + /** Returns a pointer to the first occurrence of a substring in a string. + If the substring is not found, this will return a pointer to the string's + null terminator. + */ + template + static CharPointerType1 find (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept + { + const int substringLength = (int) substringToLookFor.length(); + + while (textToSearch.compareUpTo (substringToLookFor, substringLength) != 0 + && ! textToSearch.isEmpty()) + ++textToSearch; + + return textToSearch; + } + + /** Finds the character index of a given substring in another string, using + a case-independent match. + Returns -1 if the substring is not found. + */ + template + static int indexOfIgnoreCase (CharPointerType1 haystack, const CharPointerType2 needle) noexcept + { + int index = 0; + const int needleLength = (int) needle.length(); + + for (;;) + { + if (haystack.compareIgnoreCaseUpTo (needle, needleLength) == 0) + return index; + + if (haystack.getAndAdvance() == 0) + return -1; + + ++index; + } + } + + /** Finds the character index of a given character in another string. + Returns -1 if the character is not found. + */ + template + static int indexOfChar (Type text, const beast_wchar charToFind) noexcept + { + int i = 0; + + while (! text.isEmpty()) + { + if (text.getAndAdvance() == charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Finds the character index of a given character in another string, using + a case-independent match. + Returns -1 if the character is not found. + */ + template + static int indexOfCharIgnoreCase (Type text, beast_wchar charToFind) noexcept + { + charToFind = CharacterFunctions::toLowerCase (charToFind); + int i = 0; + + while (! text.isEmpty()) + { + if (text.toLowerCase() == charToFind) + return i; + + ++text; + ++i; + } + + return -1; + } + + /** Returns a pointer to the first non-whitespace character in a string. + If the string contains only whitespace, this will return a pointer + to its null terminator. + */ + template + static Type findEndOfWhitespace (const Type& text) noexcept + { + Type p (text); + + while (p.isWhitespace()) + ++p; + + return p; + } + + /** Returns a pointer to the first character in the string which is found in + the breakCharacters string. + */ + template + static Type findEndOfToken (const Type& text, const Type& breakCharacters, const Type& quoteCharacters) + { + Type t (text); + beast_wchar currentQuoteChar = 0; + + while (! t.isEmpty()) + { + const beast_wchar c = t.getAndAdvance(); + + if (currentQuoteChar == 0 && breakCharacters.indexOf (c) >= 0) + { + --t; + break; + } + + if (quoteCharacters.indexOf (c) >= 0) + { + if (currentQuoteChar == 0) + currentQuoteChar = c; + else if (currentQuoteChar == c) + currentQuoteChar = 0; + } + } + + return t; + } + +private: + static double mulexp10 (const double value, int exponent) noexcept; +}; + + +#endif // BEAST_CHARACTERFUNCTIONS_BEASTHEADER diff --git a/modules/beast_core/text/beast_Identifier.cpp b/modules/beast_core/text/beast_Identifier.cpp new file mode 100644 index 0000000000..f003ad0034 --- /dev/null +++ b/modules/beast_core/text/beast_Identifier.cpp @@ -0,0 +1,69 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +StringPool& Identifier::getPool() +{ + static StringPool pool; + return pool; +} + +Identifier::Identifier() noexcept + : name (nullptr) +{ +} + +Identifier::Identifier (const Identifier& other) noexcept + : name (other.name) +{ +} + +Identifier& Identifier::operator= (const Identifier other) noexcept +{ + name = other.name; + return *this; +} + +Identifier::Identifier (const String& nm) + : name (Identifier::getPool().getPooledString (nm)) +{ +} + +Identifier::Identifier (const char* const nm) + : name (Identifier::getPool().getPooledString (nm)) +{ + /* An Identifier string must be suitable for use as a script variable or XML + attribute, so it can only contain this limited set of characters.. */ + bassert (isValidIdentifier (toString())); +} + +Identifier::~Identifier() +{ +} + +Identifier Identifier::null; + +bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept +{ + return possibleIdentifier.isNotEmpty() + && possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%"); +} diff --git a/modules/beast_core/text/beast_Identifier.h b/modules/beast_core/text/beast_Identifier.h new file mode 100644 index 0000000000..f3157bcfee --- /dev/null +++ b/modules/beast_core/text/beast_Identifier.h @@ -0,0 +1,106 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_IDENTIFIER_BEASTHEADER +#define BEAST_IDENTIFIER_BEASTHEADER + +class StringPool; + + +//============================================================================== +/** + Represents a string identifier, designed for accessing properties by name. + + Identifier objects are very light and fast to copy, but slower to initialise + from a string, so it's much faster to keep a static identifier object to refer + to frequently-used names, rather than constructing them each time you need it. + + @see NamedPropertySet, ValueTree +*/ +class BEAST_API Identifier +{ +public: + /** Creates a null identifier. */ + Identifier() noexcept; + + /** Creates an identifier with a specified name. + Because this name may need to be used in contexts such as script variables or XML + tags, it must only contain ascii letters and digits, or the underscore character. + */ + Identifier (const char* name); + + /** Creates an identifier with a specified name. + Because this name may need to be used in contexts such as script variables or XML + tags, it must only contain ascii letters and digits, or the underscore character. + */ + Identifier (const String& name); + + /** Creates a copy of another identifier. */ + Identifier (const Identifier& other) noexcept; + + /** Creates a copy of another identifier. */ + Identifier& operator= (const Identifier other) noexcept; + + /** Destructor */ + ~Identifier(); + + /** Compares two identifiers. This is a very fast operation. */ + inline bool operator== (const Identifier other) const noexcept { return name == other.name; } + + /** Compares two identifiers. This is a very fast operation. */ + inline bool operator!= (const Identifier other) const noexcept { return name != other.name; } + + /** Returns this identifier as a string. */ + String toString() const { return name; } + + /** Returns this identifier's raw string pointer. */ + operator const String::CharPointerType() const noexcept { return name; } + + /** Returns this identifier's raw string pointer. */ + const String::CharPointerType getCharPointer() const noexcept { return name; } + + /** Returns true if this Identifier is not null */ + bool isValid() const noexcept { return name.getAddress() != nullptr; } + + /** Returns true if this Identifier is null */ + bool isNull() const noexcept { return name.getAddress() == nullptr; } + + /** A null identifier. */ + static Identifier null; + + /** Checks a given string for characters that might not be valid in an Identifier. + Since Identifiers are used as a script variables and XML attributes, they should only contain + alphanumeric characters, underscores, or the '-' and ':' characters. + */ + static bool isValidIdentifier (const String& possibleIdentifier) noexcept; + + +private: + //============================================================================== + String::CharPointerType name; + + static StringPool& getPool(); +}; + + +#endif // BEAST_IDENTIFIER_BEASTHEADER diff --git a/modules/beast_core/text/beast_LocalisedStrings.cpp b/modules/beast_core/text/beast_LocalisedStrings.cpp new file mode 100644 index 0000000000..95866d4917 --- /dev/null +++ b/modules/beast_core/text/beast_LocalisedStrings.cpp @@ -0,0 +1,182 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +LocalisedStrings::LocalisedStrings (const String& fileContents, bool ignoreCase) +{ + loadFromText (fileContents, ignoreCase); +} + +LocalisedStrings::LocalisedStrings (const File& fileToLoad, bool ignoreCase) +{ + loadFromText (fileToLoad.loadFileAsString(), ignoreCase); +} + +LocalisedStrings::~LocalisedStrings() +{ +} + +//============================================================================== +String LocalisedStrings::translate (const String& text) const +{ + return translations.getValue (text, text); +} + +String LocalisedStrings::translate (const String& text, const String& resultIfNotFound) const +{ + return translations.getValue (text, resultIfNotFound); +} + +namespace +{ + #if BEAST_CHECK_MEMORY_LEAKS + // By using this object to force a LocalisedStrings object to be created + // before the currentMappings object, we can force the static order-of-destruction to + // delete the currentMappings object first, which avoids a bogus leak warning. + // (Oddly, just creating a LocalisedStrings on the stack doesn't work in gcc, it + // has to be created with 'new' for this to work..) + struct LeakAvoidanceTrick + { + LeakAvoidanceTrick() + { + const ScopedPointer dummy (new LocalisedStrings (String(), false)); + } + }; + + LeakAvoidanceTrick leakAvoidanceTrick; + #endif + + SpinLock currentMappingsLock; + ScopedPointer currentMappings; + + int findCloseQuote (const String& text, int startPos) + { + beast_wchar lastChar = 0; + String::CharPointerType t (text.getCharPointer() + startPos); + + for (;;) + { + const beast_wchar c = t.getAndAdvance(); + + if (c == 0 || (c == '"' && lastChar != '\\')) + break; + + lastChar = c; + ++startPos; + } + + return startPos; + } + + String unescapeString (const String& s) + { + return s.replace ("\\\"", "\"") + .replace ("\\\'", "\'") + .replace ("\\t", "\t") + .replace ("\\r", "\r") + .replace ("\\n", "\n"); + } +} + +void LocalisedStrings::loadFromText (const String& fileContents, bool ignoreCase) +{ + translations.setIgnoresCase (ignoreCase); + + StringArray lines; + lines.addLines (fileContents); + + for (int i = 0; i < lines.size(); ++i) + { + String line (lines[i].trim()); + + if (line.startsWithChar ('"')) + { + int closeQuote = findCloseQuote (line, 1); + + const String originalText (unescapeString (line.substring (1, closeQuote))); + + if (originalText.isNotEmpty()) + { + const int openingQuote = findCloseQuote (line, closeQuote + 1); + closeQuote = findCloseQuote (line, openingQuote + 1); + + const String newText (unescapeString (line.substring (openingQuote + 1, closeQuote))); + + if (newText.isNotEmpty()) + translations.set (originalText, newText); + } + } + else if (line.startsWithIgnoreCase ("language:")) + { + languageName = line.substring (9).trim(); + } + else if (line.startsWithIgnoreCase ("countries:")) + { + countryCodes.addTokens (line.substring (10).trim(), true); + countryCodes.trim(); + countryCodes.removeEmptyStrings(); + } + } +} + +//============================================================================== +void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) +{ + const SpinLock::ScopedLockType sl (currentMappingsLock); + currentMappings = newTranslations; +} + +LocalisedStrings* LocalisedStrings::getCurrentMappings() +{ + return currentMappings; +} + +String LocalisedStrings::translateWithCurrentMappings (const String& text) +{ + return beast::translate (text); +} + +String LocalisedStrings::translateWithCurrentMappings (const char* text) +{ + return beast::translate (String (text)); +} + +String translate (const String& text) +{ + return translate (text, text); +} + +String translate (const char* const literal) +{ + const String text (literal); + return translate (text, text); +} + +String translate (const String& text, const String& resultIfNotFound) +{ + const SpinLock::ScopedLockType sl (currentMappingsLock); + + if (const LocalisedStrings* const mappings = LocalisedStrings::getCurrentMappings()) + return mappings->translate (text, resultIfNotFound); + + return resultIfNotFound; +} diff --git a/modules/beast_core/text/beast_LocalisedStrings.h b/modules/beast_core/text/beast_LocalisedStrings.h new file mode 100644 index 0000000000..8e805ec007 --- /dev/null +++ b/modules/beast_core/text/beast_LocalisedStrings.h @@ -0,0 +1,219 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_LOCALISEDSTRINGS_BEASTHEADER +#define BEAST_LOCALISEDSTRINGS_BEASTHEADER + +#include "beast_StringPairArray.h" +#include "../files/beast_File.h" + +//============================================================================== +/** + Used to convert strings to localised foreign-language versions. + + This is basically a look-up table of strings and their translated equivalents. + It can be loaded from a text file, so that you can supply a set of localised + versions of strings that you use in your app. + + To use it in your code, simply call the translate() method on each string that + might have foreign versions, and if none is found, the method will just return + the original string. + + The translation file should start with some lines specifying a description of + the language it contains, and also a list of ISO country codes where it might + be appropriate to use the file. After that, each line of the file should contain + a pair of quoted strings with an '=' sign. + + E.g. for a french translation, the file might be: + + @code + language: French + countries: fr be mc ch lu + + "hello" = "bonjour" + "goodbye" = "au revoir" + @endcode + + If the strings need to contain a quote character, they can use '\"' instead, and + if the first non-whitespace character on a line isn't a quote, then it's ignored, + (you can use this to add comments). + + Note that this is a singleton class, so don't create or destroy the object directly. + There's also a TRANS(text) macro defined to make it easy to use the this. + + E.g. @code + printSomething (TRANS("hello")); + @endcode + + This macro is used in the Beast classes themselves, so your application has a chance to + intercept and translate any internal Beast text strings that might be shown. (You can easily + get a list of all the messages by searching for the TRANS() macro in the Beast source + code). +*/ +class BEAST_API LocalisedStrings +{ +public: + //============================================================================== + /** Creates a set of translations from the text of a translation file. + + When you create one of these, you can call setCurrentMappings() to make it + the set of mappings that the system's using. + */ + LocalisedStrings (const String& fileContents, + bool ignoreCaseOfKeys); + + /** Creates a set of translations from a file. + + When you create one of these, you can call setCurrentMappings() to make it + the set of mappings that the system's using. + */ + LocalisedStrings (const File& fileToLoad, + bool ignoreCaseOfKeys); + + /** Destructor. */ + ~LocalisedStrings(); + + //============================================================================== + /** Selects the current set of mappings to be used by the system. + + The object you pass in will be automatically deleted when no longer needed, so + don't keep a pointer to it. You can also pass in zero to remove the current + mappings. + + See also the TRANS() macro, which uses the current set to do its translation. + + @see translateWithCurrentMappings + */ + static void setCurrentMappings (LocalisedStrings* newTranslations); + + /** Returns the currently selected set of mappings. + + This is the object that was last passed to setCurrentMappings(). It may + be nullptr if none has been created. + */ + static LocalisedStrings* getCurrentMappings(); + + /** Tries to translate a string using the currently selected set of mappings. + + If no mapping has been set, or if the mapping doesn't contain a translation + for the string, this will just return the original string. + + See also the TRANS() macro, which uses this method to do its translation. + + @see setCurrentMappings, getCurrentMappings + */ + static String translateWithCurrentMappings (const String& text); + + /** Tries to translate a string using the currently selected set of mappings. + + If no mapping has been set, or if the mapping doesn't contain a translation + for the string, this will just return the original string. + + See also the TRANS() macro, which uses this method to do its translation. + + @see setCurrentMappings, getCurrentMappings + */ + static String translateWithCurrentMappings (const char* text); + + //============================================================================== + /** Attempts to look up a string and return its localised version. + If the string isn't found in the list, the original string will be returned. + */ + String translate (const String& text) const; + + /** Attempts to look up a string and return its localised version. + If the string isn't found in the list, the resultIfNotFound string will be returned. + */ + String translate (const String& text, const String& resultIfNotFound) const; + + /** Returns the name of the language specified in the translation file. + + This is specified in the file using a line starting with "language:", e.g. + @code + language: german + @endcode + */ + String getLanguageName() const { return languageName; } + + /** Returns the list of suitable country codes listed in the translation file. + + These is specified in the file using a line starting with "countries:", e.g. + @code + countries: fr be mc ch lu + @endcode + + The country codes are supposed to be 2-character ISO complient codes. + */ + const StringArray& getCountryCodes() const { return countryCodes; } + + /** Provides access to the actual list of mappings. */ + const StringPairArray& getMappings() const { return translations; } + +private: + //============================================================================== + String languageName; + StringArray countryCodes; + StringPairArray translations; + + void loadFromText (const String&, bool ignoreCase); + + BEAST_LEAK_DETECTOR (LocalisedStrings) +}; + +//============================================================================== +#ifndef TRANS + /** Uses the LocalisedStrings class to translate the given string literal. + This macro is provided for backwards-compatibility, and just calls the translate() + function. In new code, it's recommended that you just call translate() directly + instead, and avoid using macros. + @see translate(), LocalisedStrings + */ + #define TRANS(stringLiteral) beast::translate (stringLiteral) +#endif + +/** A dummy version of the TRANS macro, used to indicate a string literal that should be + added to the translation file by source-code scanner tools. + + Wrapping a string literal in this macro has no effect, but by using it around strings + that your app needs to translate at a later stage, it lets automatic code-scanning tools + find this string and add it to the list of strings that need translation. +*/ +#define NEEDS_TRANS(stringLiteral) (stringLiteral) + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +String translate (const String& stringLiteral); + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +String translate (const char* stringLiteral); + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +String translate (const String& stringLiteral, const String& resultIfNotFound); + + +#endif // BEAST_LOCALISEDSTRINGS_BEASTHEADER diff --git a/modules/beast_core/text/beast_NewLine.h b/modules/beast_core/text/beast_NewLine.h new file mode 100644 index 0000000000..4256d29470 --- /dev/null +++ b/modules/beast_core/text/beast_NewLine.h @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_NEWLINE_BEASTHEADER +#define BEAST_NEWLINE_BEASTHEADER + + +//============================================================================== +/** This class is used for represent a new-line character sequence. + + To write a new-line to a stream, you can use the predefined 'newLine' variable, e.g. + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode + + The exact character sequence that will be used for the new-line can be set and + retrieved with OutputStream::setNewLineString() and OutputStream::getNewLineString(). +*/ +class BEAST_API NewLine +{ +public: + /** Returns the default new-line sequence that the library uses. + @see OutputStream::setNewLineString() + */ + static const char* getDefault() noexcept { return "\r\n"; } + + /** Returns the default new-line sequence that the library uses. + @see getDefault() + */ + operator String() const { return getDefault(); } +}; + +//============================================================================== +/** A predefined object representing a new-line, which can be written to a string or stream. + + To write a new-line to a stream, you can use the predefined 'newLine' variable like this: + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode +*/ +extern NewLine newLine; + +//============================================================================== +/** Writes a new-line sequence to a string. + You can use the predefined object 'newLine' to invoke this, e.g. + @code + myString << "Hello World" << newLine << newLine; + @endcode +*/ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, const NewLine&); + + +#endif // BEAST_NEWLINE_BEASTHEADER diff --git a/modules/beast_core/text/beast_String.cpp b/modules/beast_core/text/beast_String.cpp new file mode 100644 index 0000000000..87de39ad3f --- /dev/null +++ b/modules/beast_core/text/beast_String.cpp @@ -0,0 +1,2406 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +NewLine newLine; + +#if defined (BEAST_STRINGS_ARE_UNICODE) && ! BEAST_STRINGS_ARE_UNICODE + #error "BEAST_STRINGS_ARE_UNICODE is deprecated! All strings are now unicode by default." +#endif + +#if BEAST_NATIVE_WCHAR_IS_UTF8 + typedef CharPointer_UTF8 CharPointer_wchar_t; +#elif BEAST_NATIVE_WCHAR_IS_UTF16 + typedef CharPointer_UTF16 CharPointer_wchar_t; +#else + typedef CharPointer_UTF32 CharPointer_wchar_t; +#endif + +static inline CharPointer_wchar_t castToCharPointer_wchar_t (const void* t) noexcept +{ + return CharPointer_wchar_t (static_cast (t)); +} + +//============================================================================== +class StringHolder +{ +public: + StringHolder() noexcept + : refCount (0x3fffffff), allocatedNumBytes (sizeof (*text)) + { + text[0] = 0; + } + + typedef String::CharPointerType CharPointerType; + typedef String::CharPointerType::CharType CharType; + + //============================================================================== + static CharPointerType createUninitialisedBytes (const size_t numBytes) + { + StringHolder* const s = reinterpret_cast (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); + s->refCount.value = 0; + s->allocatedNumBytes = numBytes; + return CharPointerType (s->text); + } + + template + static CharPointerType createFromCharPointer (const CharPointer text) + { + if (text.getAddress() == nullptr || text.isEmpty()) + return getEmpty(); + + CharPointer t (text); + size_t bytesNeeded = sizeof (CharType); + + while (! t.isEmpty()) + bytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeAll (text); + return dest; + } + + template + static CharPointerType createFromCharPointer (const CharPointer text, size_t maxChars) + { + if (text.getAddress() == nullptr || text.isEmpty() || maxChars == 0) + return getEmpty(); + + CharPointer end (text); + size_t numChars = 0; + size_t bytesNeeded = sizeof (CharType); + + while (numChars < maxChars && ! end.isEmpty()) + { + bytesNeeded += CharPointerType::getBytesRequiredFor (end.getAndAdvance()); + ++numChars; + } + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeWithCharLimit (text, (int) numChars + 1); + return dest; + } + + template + static CharPointerType createFromCharPointer (const CharPointer start, const CharPointer end) + { + if (start.getAddress() == nullptr || start.isEmpty()) + return getEmpty(); + + CharPointer e (start); + int numChars = 0; + size_t bytesNeeded = sizeof (CharType); + + while (e < end && ! e.isEmpty()) + { + bytesNeeded += CharPointerType::getBytesRequiredFor (e.getAndAdvance()); + ++numChars; + } + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeWithCharLimit (start, numChars + 1); + return dest; + } + + static CharPointerType createFromCharPointer (const CharPointerType start, const CharPointerType end) + { + if (start.getAddress() == nullptr || start.isEmpty()) + return getEmpty(); + + const size_t numBytes = (size_t) (end.getAddress() - start.getAddress()); + const CharPointerType dest (createUninitialisedBytes (numBytes + 1)); + memcpy (dest.getAddress(), start, numBytes); + dest.getAddress()[numBytes] = 0; + return dest; + } + + static CharPointerType createFromFixedLength (const char* const src, const size_t numChars) + { + const CharPointerType dest (createUninitialisedBytes (numChars * sizeof (CharType) + sizeof (CharType))); + CharPointerType (dest).writeWithCharLimit (CharPointer_UTF8 (src), (int) (numChars + 1)); + return dest; + } + + static inline CharPointerType getEmpty() noexcept + { + return CharPointerType (empty.text); + } + + //============================================================================== + static void retain (const CharPointerType text) noexcept + { + ++(bufferFromText (text)->refCount); + } + + static inline void release (StringHolder* const b) noexcept + { + if (--(b->refCount) == -1 && b != &empty) + delete[] reinterpret_cast (b); + } + + static void release (const CharPointerType text) noexcept + { + release (bufferFromText (text)); + } + + //============================================================================== + static CharPointerType makeUnique (const CharPointerType text) + { + StringHolder* const b = bufferFromText (text); + + if (b->refCount.get() <= 0) + return text; + + CharPointerType newText (createUninitialisedBytes (b->allocatedNumBytes)); + memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes); + release (b); + + return newText; + } + + static CharPointerType makeUniqueWithByteSize (const CharPointerType text, size_t numBytes) + { + StringHolder* const b = bufferFromText (text); + + if (b->refCount.get() <= 0 && b->allocatedNumBytes >= numBytes) + return text; + + CharPointerType newText (createUninitialisedBytes (bmax (b->allocatedNumBytes, numBytes))); + memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes); + release (b); + + return newText; + } + + static size_t getAllocatedNumBytes (const CharPointerType text) noexcept + { + return bufferFromText (text)->allocatedNumBytes; + } + + //============================================================================== + Atomic refCount; + size_t allocatedNumBytes; + CharType text[1]; + + static StringHolder empty; + +private: + static inline StringHolder* bufferFromText (const CharPointerType text) noexcept + { + // (Can't use offsetof() here because of warnings about this not being a POD) + return reinterpret_cast (reinterpret_cast (text.getAddress()) + - (reinterpret_cast (reinterpret_cast (1)->text) - 1)); + } + + void compileTimeChecks() + { + // Let me know if any of these assertions fail on your system! + #if BEAST_NATIVE_WCHAR_IS_UTF8 + static_bassert (sizeof (wchar_t) == 1); + #elif BEAST_NATIVE_WCHAR_IS_UTF16 + static_bassert (sizeof (wchar_t) == 2); + #elif BEAST_NATIVE_WCHAR_IS_UTF32 + static_bassert (sizeof (wchar_t) == 4); + #else + #error "native wchar_t size is unknown" + #endif + } +}; + +StringHolder StringHolder::empty; +const String String::empty; + +//============================================================================== +void String::preallocateBytes (const size_t numBytesNeeded) +{ + text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType)); +} + +//============================================================================== +String::String() noexcept : text (StringHolder::getEmpty()) +{ +} + +String::~String() noexcept +{ + StringHolder::release (text); +} + +String::String (const String& other) noexcept + : text (other.text) +{ + StringHolder::retain (text); +} + +void String::swapWith (String& other) noexcept +{ + std::swap (text, other.text); +} + +String& String::operator= (const String& other) noexcept +{ + StringHolder::retain (other.text); + StringHolder::release (text.atomicSwap (other.text)); + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +String::String (String&& other) noexcept + : text (other.text) +{ + other.text = StringHolder::getEmpty(); +} + +String& String::operator= (String&& other) noexcept +{ + std::swap (text, other.text); + return *this; +} +#endif + +inline String::PreallocationBytes::PreallocationBytes (const size_t numBytes_) : numBytes (numBytes_) {} + +String::String (const PreallocationBytes& preallocationSize) + : text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType))) +{ +} + +//============================================================================== +String::String (const char* const t) + : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t))) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + */ + bassert (t == nullptr || CharPointer_ASCII::isValidString (t, std::numeric_limits::max())); +} + +String::String (const char* const t, const size_t maxChars) + : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t), maxChars)) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + */ + bassert (t == nullptr || CharPointer_ASCII::isValidString (t, (int) maxChars)); +} + +String::String (const wchar_t* const t) : text (StringHolder::createFromCharPointer (castToCharPointer_wchar_t (t))) {} +String::String (const CharPointer_UTF8 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_UTF16 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_UTF32 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_ASCII t) : text (StringHolder::createFromCharPointer (t)) {} + +String::String (const CharPointer_UTF8 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const CharPointer_UTF16 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const CharPointer_UTF32 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const wchar_t* const t, size_t maxChars) : text (StringHolder::createFromCharPointer (castToCharPointer_wchar_t (t), maxChars)) {} + +String::String (const CharPointer_UTF8 start, const CharPointer_UTF8 end) : text (StringHolder::createFromCharPointer (start, end)) {} +String::String (const CharPointer_UTF16 start, const CharPointer_UTF16 end) : text (StringHolder::createFromCharPointer (start, end)) {} +String::String (const CharPointer_UTF32 start, const CharPointer_UTF32 end) : text (StringHolder::createFromCharPointer (start, end)) {} + +String::String (const std::string& s) : text (StringHolder::createFromFixedLength (s.data(), s.size())) {} + +String String::charToString (const beast_wchar character) +{ + String result (PreallocationBytes (CharPointerType::getBytesRequiredFor (character))); + CharPointerType t (result.text); + t.write (character); + t.writeNull(); + return result; +} + +//============================================================================== +namespace NumberToStringConverters +{ + // pass in a pointer to the END of a buffer.. + static char* numberToString (char* t, const int64 n) noexcept + { + *--t = 0; + int64 v = (n >= 0) ? n : -n; + + do + { + *--t = (char) ('0' + (int) (v % 10)); + v /= 10; + + } while (v > 0); + + if (n < 0) + *--t = '-'; + + return t; + } + + static char* numberToString (char* t, uint64 v) noexcept + { + *--t = 0; + + do + { + *--t = (char) ('0' + (int) (v % 10)); + v /= 10; + + } while (v > 0); + + return t; + } + + static char* numberToString (char* t, const int n) noexcept + { + if (n == (int) 0x80000000) // (would cause an overflow) + return numberToString (t, (int64) n); + + *--t = 0; + int v = abs (n); + + do + { + *--t = (char) ('0' + (v % 10)); + v /= 10; + + } while (v > 0); + + if (n < 0) + *--t = '-'; + + return t; + } + + static char* numberToString (char* t, unsigned int v) noexcept + { + *--t = 0; + + do + { + *--t = (char) ('0' + (v % 10)); + v /= 10; + + } while (v > 0); + + return t; + } + + static char* doubleToString (char* buffer, const int numChars, double n, int numDecPlaces, size_t& len) noexcept + { + if (numDecPlaces > 0 && numDecPlaces < 7 && n > -1.0e20 && n < 1.0e20) + { + char* const end = buffer + numChars; + char* t = end; + int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5); + *--t = (char) 0; + + while (numDecPlaces >= 0 || v > 0) + { + if (numDecPlaces == 0) + *--t = '.'; + + *--t = (char) ('0' + (v % 10)); + + v /= 10; + --numDecPlaces; + } + + if (n < 0) + *--t = '-'; + + len = (size_t) (end - t - 1); + return t; + } + + // Use a locale-free sprintf where possible (not available on linux AFAICT) + #if BEAST_MSVC + static _locale_t cLocale = _create_locale (LC_NUMERIC, "C"); + + len = (size_t) (numDecPlaces > 0 ? _sprintf_l (buffer, "%.*f", cLocale, numDecPlaces, n) + : _sprintf_l (buffer, "%.9g", cLocale, n)); + #elif BEAST_MAC || BEAST_IOS + len = (size_t) (numDecPlaces > 0 ? sprintf_l (buffer, nullptr, "%.*f", numDecPlaces, n) + : sprintf_l (buffer, nullptr, "%.9g", n)); + #else + len = (size_t) (numDecPlaces > 0 ? sprintf (buffer, "%.*f", numDecPlaces, n) + : sprintf (buffer, "%.9g", n)); + #endif + + return buffer; + } + + template + static String::CharPointerType createFromInteger (const IntegerType number) + { + char buffer [32]; + char* const end = buffer + numElementsInArray (buffer); + char* const start = numberToString (end, number); + + return StringHolder::createFromFixedLength (start, (size_t) (end - start - 1)); + } + + static String::CharPointerType createFromDouble (const double number, const int numberOfDecimalPlaces) + { + char buffer [48]; + size_t len; + char* const start = doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len); + return StringHolder::createFromFixedLength (start, len); + } +} + +//============================================================================== +String::String (const int number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const unsigned int number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const short number) : text (NumberToStringConverters::createFromInteger ((int) number)) {} +String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} +String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {} + +String::String (const float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {} +String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} +String::String (const float number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble ((double) number, numberOfDecimalPlaces)) {} +String::String (const double number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble (number, numberOfDecimalPlaces)) {} + +//============================================================================== +int String::length() const noexcept +{ + return (int) text.length(); +} + +size_t String::getByteOffsetOfEnd() const noexcept +{ + return (size_t) (((char*) text.findTerminatingNull().getAddress()) - (char*) text.getAddress()); +} + +beast_wchar String::operator[] (int index) const noexcept +{ + bassert (index == 0 || (index > 0 && index <= (int) text.lengthUpTo ((size_t) index + 1))); + return text [index]; +} + +int String::hashCode() const noexcept +{ + CharPointerType t (text); + int result = 0; + + while (! t.isEmpty()) + result = 31 * result + (int) t.getAndAdvance(); + + return result; +} + +int64 String::hashCode64() const noexcept +{ + CharPointerType t (text); + int64 result = 0; + + while (! t.isEmpty()) + result = 101 * result + t.getAndAdvance(); + + return result; +} + +//============================================================================== +BEAST_API bool BEAST_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } +BEAST_API bool BEAST_CALLTYPE operator== (const String& s1, const char* const s2) noexcept { return s1.compare (s2) == 0; } +BEAST_API bool BEAST_CALLTYPE operator== (const String& s1, const wchar_t* const s2) noexcept { return s1.compare (s2) == 0; } +BEAST_API bool BEAST_CALLTYPE operator== (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +BEAST_API bool BEAST_CALLTYPE operator== (const String& s1, const CharPointer_UTF16 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +BEAST_API bool BEAST_CALLTYPE operator== (const String& s1, const CharPointer_UTF32 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +BEAST_API bool BEAST_CALLTYPE operator!= (const String& s1, const String& s2) noexcept { return s1.compare (s2) != 0; } +BEAST_API bool BEAST_CALLTYPE operator!= (const String& s1, const char* const s2) noexcept { return s1.compare (s2) != 0; } +BEAST_API bool BEAST_CALLTYPE operator!= (const String& s1, const wchar_t* const s2) noexcept { return s1.compare (s2) != 0; } +BEAST_API bool BEAST_CALLTYPE operator!= (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +BEAST_API bool BEAST_CALLTYPE operator!= (const String& s1, const CharPointer_UTF16 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +BEAST_API bool BEAST_CALLTYPE operator!= (const String& s1, const CharPointer_UTF32 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +BEAST_API bool BEAST_CALLTYPE operator> (const String& s1, const String& s2) noexcept { return s1.compare (s2) > 0; } +BEAST_API bool BEAST_CALLTYPE operator< (const String& s1, const String& s2) noexcept { return s1.compare (s2) < 0; } +BEAST_API bool BEAST_CALLTYPE operator>= (const String& s1, const String& s2) noexcept { return s1.compare (s2) >= 0; } +BEAST_API bool BEAST_CALLTYPE operator<= (const String& s1, const String& s2) noexcept { return s1.compare (s2) <= 0; } + +bool String::equalsIgnoreCase (const wchar_t* const t) const noexcept +{ + return t != nullptr ? text.compareIgnoreCase (castToCharPointer_wchar_t (t)) == 0 + : isEmpty(); +} + +bool String::equalsIgnoreCase (const char* const t) const noexcept +{ + return t != nullptr ? text.compareIgnoreCase (CharPointer_UTF8 (t)) == 0 + : isEmpty(); +} + +bool String::equalsIgnoreCase (const String& other) const noexcept +{ + return text == other.text + || text.compareIgnoreCase (other.text) == 0; +} + +int String::compare (const String& other) const noexcept { return (text == other.text) ? 0 : text.compare (other.text); } +int String::compare (const char* const other) const noexcept { return text.compare (CharPointer_UTF8 (other)); } +int String::compare (const wchar_t* const other) const noexcept { return text.compare (castToCharPointer_wchar_t (other)); } +int String::compareIgnoreCase (const String& other) const noexcept { return (text == other.text) ? 0 : text.compareIgnoreCase (other.text); } + +int String::compareLexicographically (const String& other) const noexcept +{ + CharPointerType s1 (text); + + while (! (s1.isEmpty() || s1.isLetterOrDigit())) + ++s1; + + CharPointerType s2 (other.text); + + while (! (s2.isEmpty() || s2.isLetterOrDigit())) + ++s2; + + return s1.compareIgnoreCase (s2); +} + +//============================================================================== +void String::append (const String& textToAppend, size_t maxCharsToTake) +{ + appendCharPointer (textToAppend.text, maxCharsToTake); +} + +String& String::operator+= (const wchar_t* const t) +{ + appendCharPointer (castToCharPointer_wchar_t (t)); + return *this; +} + +String& String::operator+= (const char* const t) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + */ + bassert (t == nullptr || CharPointer_ASCII::isValidString (t, std::numeric_limits::max())); + + appendCharPointer (CharPointer_ASCII (t)); + return *this; +} + +String& String::operator+= (const String& other) +{ + if (isEmpty()) + return operator= (other); + + appendCharPointer (other.text); + return *this; +} + +String& String::operator+= (const char ch) +{ + const char asString[] = { ch, 0 }; + return operator+= (asString); +} + +String& String::operator+= (const wchar_t ch) +{ + const wchar_t asString[] = { ch, 0 }; + return operator+= (asString); +} + +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +String& String::operator+= (const beast_wchar ch) +{ + const beast_wchar asString[] = { ch, 0 }; + appendCharPointer (CharPointer_UTF32 (asString)); + return *this; +} +#endif + +String& String::operator+= (const int number) +{ + char buffer [16]; + char* const end = buffer + numElementsInArray (buffer); + char* const start = NumberToStringConverters::numberToString (end, number); + + const int numExtraChars = (int) (end - start); + + if (numExtraChars > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + const size_t newBytesNeeded = sizeof (CharPointerType::CharType) + byteOffsetOfNull + + sizeof (CharPointerType::CharType) * (size_t) numExtraChars; + + text = StringHolder::makeUniqueWithByteSize (text, newBytesNeeded); + + CharPointerType newEnd (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)); + newEnd.writeWithCharLimit (CharPointer_ASCII (start), numExtraChars); + } + + return *this; +} + +//============================================================================== +BEAST_API String BEAST_CALLTYPE operator+ (const char* const string1, const String& string2) +{ + String s (string1); + return s += string2; +} + +BEAST_API String BEAST_CALLTYPE operator+ (const wchar_t* const string1, const String& string2) +{ + String s (string1); + return s += string2; +} + +BEAST_API String BEAST_CALLTYPE operator+ (const char s1, const String& s2) { return String::charToString ((beast_wchar) (uint8) s1) + s2; } +BEAST_API String BEAST_CALLTYPE operator+ (const wchar_t s1, const String& s2) { return String::charToString (s1) + s2; } +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +BEAST_API String BEAST_CALLTYPE operator+ (const beast_wchar s1, const String& s2) { return String::charToString (s1) + s2; } +#endif + +BEAST_API String BEAST_CALLTYPE operator+ (String s1, const String& s2) { return s1 += s2; } +BEAST_API String BEAST_CALLTYPE operator+ (String s1, const char* const s2) { return s1 += s2; } +BEAST_API String BEAST_CALLTYPE operator+ (String s1, const wchar_t* s2) { return s1 += s2; } + +BEAST_API String BEAST_CALLTYPE operator+ (String s1, const char s2) { return s1 += s2; } +BEAST_API String BEAST_CALLTYPE operator+ (String s1, const wchar_t s2) { return s1 += s2; } +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +BEAST_API String BEAST_CALLTYPE operator+ (String s1, const beast_wchar s2) { return s1 += s2; } +#endif + +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const char s2) { return s1 += s2; } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const wchar_t s2) { return s1 += s2; } +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const beast_wchar s2) { return s1 += s2; } +#endif + +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const char* const s2) { return s1 += s2; } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const wchar_t* const s2) { return s1 += s2; } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const String& s2) { return s1 += s2; } + +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const long number) { return s1 += (int) number; } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const int64 number) { return s1 << String (number); } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } +BEAST_API String& BEAST_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } + +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const String& text) +{ + const size_t numBytes = text.getNumBytesAsUTF8(); + + #if (BEAST_STRING_UTF_TYPE == 8) + stream.write (text.getCharPointer().getAddress(), numBytes); + #else + // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind + // if lots of large, persistent strings were to be written to streams). + HeapBlock temp (numBytes + 1); + CharPointer_UTF8 (temp).writeAll (text.getCharPointer()); + stream.write (temp, numBytes); + #endif + + return stream; +} + +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, const NewLine&) +{ + return string1 += NewLine::getDefault(); +} + +//============================================================================== +int String::indexOfChar (const beast_wchar character) const noexcept +{ + return text.indexOf (character); +} + +int String::indexOfChar (const int startIndex, const beast_wchar character) const noexcept +{ + CharPointerType t (text); + + for (int i = 0; ! t.isEmpty(); ++i) + { + if (i >= startIndex) + { + if (t.getAndAdvance() == character) + return i; + } + else + { + ++t; + } + } + + return -1; +} + +int String::lastIndexOfChar (const beast_wchar character) const noexcept +{ + CharPointerType t (text); + int last = -1; + + for (int i = 0; ! t.isEmpty(); ++i) + if (t.getAndAdvance() == character) + last = i; + + return last; +} + +int String::indexOfAnyOf (const String& charactersToLookFor, const int startIndex, const bool ignoreCase) const noexcept +{ + CharPointerType t (text); + + for (int i = 0; ! t.isEmpty(); ++i) + { + if (i >= startIndex) + { + if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) + return i; + } + else + { + ++t; + } + } + + return -1; +} + +int String::indexOf (const String& other) const noexcept +{ + return other.isEmpty() ? 0 : text.indexOf (other.text); +} + +int String::indexOfIgnoreCase (const String& other) const noexcept +{ + return other.isEmpty() ? 0 : CharacterFunctions::indexOfIgnoreCase (text, other.text); +} + +int String::indexOf (const int startIndex, const String& other) const noexcept +{ + if (other.isEmpty()) + return -1; + + CharPointerType t (text); + + for (int i = startIndex; --i >= 0;) + { + if (t.isEmpty()) + return -1; + + ++t; + } + + int found = t.indexOf (other.text); + if (found >= 0) + found += startIndex; + return found; +} + +int String::indexOfIgnoreCase (const int startIndex, const String& other) const noexcept +{ + if (other.isEmpty()) + return -1; + + CharPointerType t (text); + + for (int i = startIndex; --i >= 0;) + { + if (t.isEmpty()) + return -1; + + ++t; + } + + int found = CharacterFunctions::indexOfIgnoreCase (t, other.text); + if (found >= 0) + found += startIndex; + return found; +} + +int String::lastIndexOf (const String& other) const noexcept +{ + if (other.isNotEmpty()) + { + const int len = other.length(); + int i = length() - len; + + if (i >= 0) + { + CharPointerType n (text + i); + + while (i >= 0) + { + if (n.compareUpTo (other.text, len) == 0) + return i; + + --n; + --i; + } + } + } + + return -1; +} + +int String::lastIndexOfIgnoreCase (const String& other) const noexcept +{ + if (other.isNotEmpty()) + { + const int len = other.length(); + int i = length() - len; + + if (i >= 0) + { + CharPointerType n (text + i); + + while (i >= 0) + { + if (n.compareIgnoreCaseUpTo (other.text, len) == 0) + return i; + + --n; + --i; + } + } + } + + return -1; +} + +int String::lastIndexOfAnyOf (const String& charactersToLookFor, const bool ignoreCase) const noexcept +{ + CharPointerType t (text); + int last = -1; + + for (int i = 0; ! t.isEmpty(); ++i) + if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) + last = i; + + return last; +} + +bool String::contains (const String& other) const noexcept +{ + return indexOf (other) >= 0; +} + +bool String::containsChar (const beast_wchar character) const noexcept +{ + return text.indexOf (character) >= 0; +} + +bool String::containsIgnoreCase (const String& t) const noexcept +{ + return indexOfIgnoreCase (t) >= 0; +} + +int String::indexOfWholeWord (const String& word) const noexcept +{ + if (word.isNotEmpty()) + { + CharPointerType t (text); + const int wordLen = word.length(); + const int end = (int) t.length() - wordLen; + + for (int i = 0; i <= end; ++i) + { + if (t.compareUpTo (word.text, wordLen) == 0 + && (i == 0 || ! (t - 1).isLetterOrDigit()) + && ! (t + wordLen).isLetterOrDigit()) + return i; + + ++t; + } + } + + return -1; +} + +int String::indexOfWholeWordIgnoreCase (const String& word) const noexcept +{ + if (word.isNotEmpty()) + { + CharPointerType t (text); + const int wordLen = word.length(); + const int end = (int) t.length() - wordLen; + + for (int i = 0; i <= end; ++i) + { + if (t.compareIgnoreCaseUpTo (word.text, wordLen) == 0 + && (i == 0 || ! (t - 1).isLetterOrDigit()) + && ! (t + wordLen).isLetterOrDigit()) + return i; + + ++t; + } + } + + return -1; +} + +bool String::containsWholeWord (const String& wordToLookFor) const noexcept +{ + return indexOfWholeWord (wordToLookFor) >= 0; +} + +bool String::containsWholeWordIgnoreCase (const String& wordToLookFor) const noexcept +{ + return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0; +} + +//============================================================================== +template +struct WildCardMatcher +{ + static bool matches (CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept + { + for (;;) + { + const beast_wchar wc = wildcard.getAndAdvance(); + + if (wc == '*') + return wildcard.isEmpty() || matchesAnywhere (wildcard, test, ignoreCase); + + if (! characterMatches (wc, test.getAndAdvance(), ignoreCase)) + return false; + + if (wc == 0) + return true; + } + } + + static bool characterMatches (const beast_wchar wc, const beast_wchar tc, const bool ignoreCase) noexcept + { + return (wc == tc) || (wc == '?' && tc != 0) + || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (tc)); + } + + static bool matchesAnywhere (const CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept + { + for (; ! test.isEmpty(); ++test) + if (matches (wildcard, test, ignoreCase)) + return true; + + return false; + } +}; + +bool String::matchesWildcard (const String& wildcard, const bool ignoreCase) const noexcept +{ + return WildCardMatcher::matches (wildcard.text, text, ignoreCase); +} + +//============================================================================== +String String::repeatedString (const String& stringToRepeat, int numberOfTimesToRepeat) +{ + if (numberOfTimesToRepeat <= 0) + return empty; + + String result (PreallocationBytes (stringToRepeat.getByteOffsetOfEnd() * (size_t) numberOfTimesToRepeat)); + CharPointerType n (result.text); + + while (--numberOfTimesToRepeat >= 0) + n.writeAll (stringToRepeat.text); + + return result; +} + +String String::paddedLeft (const beast_wchar padCharacter, int minimumLength) const +{ + bassert (padCharacter != 0); + + int extraChars = minimumLength; + CharPointerType end (text); + + while (! end.isEmpty()) + { + --extraChars; + ++end; + } + + if (extraChars <= 0 || padCharacter == 0) + return *this; + + const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress()); + String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); + CharPointerType n (result.text); + + while (--extraChars >= 0) + n.write (padCharacter); + + n.writeAll (text); + return result; +} + +String String::paddedRight (const beast_wchar padCharacter, int minimumLength) const +{ + bassert (padCharacter != 0); + + int extraChars = minimumLength; + CharPointerType end (text); + + while (! end.isEmpty()) + { + --extraChars; + ++end; + } + + if (extraChars <= 0 || padCharacter == 0) + return *this; + + const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress()); + String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); + CharPointerType n (result.text); + + n.writeAll (text); + + while (--extraChars >= 0) + n.write (padCharacter); + + n.writeNull(); + return result; +} + +//============================================================================== +String String::replaceSection (int index, int numCharsToReplace, const String& stringToInsert) const +{ + if (index < 0) + { + // a negative index to replace from? + jassertfalse; + index = 0; + } + + if (numCharsToReplace < 0) + { + // replacing a negative number of characters? + numCharsToReplace = 0; + jassertfalse; + } + + int i = 0; + CharPointerType insertPoint (text); + + while (i < index) + { + if (insertPoint.isEmpty()) + { + // replacing beyond the end of the string? + jassertfalse; + return *this + stringToInsert; + } + + ++insertPoint; + ++i; + } + + CharPointerType startOfRemainder (insertPoint); + + i = 0; + while (i < numCharsToReplace && ! startOfRemainder.isEmpty()) + { + ++startOfRemainder; + ++i; + } + + if (insertPoint == text && startOfRemainder.isEmpty()) + return stringToInsert; + + const size_t initialBytes = (size_t) (((char*) insertPoint.getAddress()) - (char*) text.getAddress()); + const size_t newStringBytes = stringToInsert.getByteOffsetOfEnd(); + const size_t remainderBytes = (size_t) (((char*) startOfRemainder.findTerminatingNull().getAddress()) - (char*) startOfRemainder.getAddress()); + + const size_t newTotalBytes = initialBytes + newStringBytes + remainderBytes; + if (newTotalBytes <= 0) + return String::empty; + + String result (PreallocationBytes ((size_t) newTotalBytes)); + + char* dest = (char*) result.text.getAddress(); + memcpy (dest, text.getAddress(), initialBytes); + dest += initialBytes; + memcpy (dest, stringToInsert.text.getAddress(), newStringBytes); + dest += newStringBytes; + memcpy (dest, startOfRemainder.getAddress(), remainderBytes); + dest += remainderBytes; + CharPointerType ((CharPointerType::CharType*) dest).writeNull(); + + return result; +} + +String String::replace (const String& stringToReplace, const String& stringToInsert, const bool ignoreCase) const +{ + const int stringToReplaceLen = stringToReplace.length(); + const int stringToInsertLen = stringToInsert.length(); + + int i = 0; + String result (*this); + + while ((i = (ignoreCase ? result.indexOfIgnoreCase (i, stringToReplace) + : result.indexOf (i, stringToReplace))) >= 0) + { + result = result.replaceSection (i, stringToReplaceLen, stringToInsert); + i += stringToInsertLen; + } + + return result; +} + +class StringCreationHelper +{ +public: + StringCreationHelper (const size_t initialBytes) + : source (nullptr), dest (nullptr), allocatedBytes (initialBytes), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + StringCreationHelper (const String::CharPointerType& source_) + : source (source_), dest (nullptr), allocatedBytes (StringHolder::getAllocatedNumBytes (source)), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + void write (beast_wchar c) + { + bytesWritten += String::CharPointerType::getBytesRequiredFor (c); + + if (bytesWritten > allocatedBytes) + { + allocatedBytes += bmax ((size_t) 8, allocatedBytes / 16); + const size_t destOffset = (size_t) (((char*) dest.getAddress()) - (char*) result.getCharPointer().getAddress()); + result.preallocateBytes (allocatedBytes); + dest = addBytesToPointer (result.getCharPointer().getAddress(), (int) destOffset); + } + + dest.write (c); + } + + String result; + String::CharPointerType source; + +private: + String::CharPointerType dest; + size_t allocatedBytes, bytesWritten; +}; + +String String::replaceCharacter (const beast_wchar charToReplace, const beast_wchar charToInsert) const +{ + if (! containsChar (charToReplace)) + return *this; + + StringCreationHelper builder (text); + + for (;;) + { + beast_wchar c = builder.source.getAndAdvance(); + + if (c == charToReplace) + c = charToInsert; + + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::replaceCharacters (const String& charactersToReplace, const String& charactersToInsertInstead) const +{ + StringCreationHelper builder (text); + + for (;;) + { + beast_wchar c = builder.source.getAndAdvance(); + + const int index = charactersToReplace.indexOfChar (c); + if (index >= 0) + c = charactersToInsertInstead [index]; + + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +//============================================================================== +bool String::startsWith (const String& other) const noexcept +{ + return text.compareUpTo (other.text, other.length()) == 0; +} + +bool String::startsWithIgnoreCase (const String& other) const noexcept +{ + return text.compareIgnoreCaseUpTo (other.text, other.length()) == 0; +} + +bool String::startsWithChar (const beast_wchar character) const noexcept +{ + bassert (character != 0); // strings can't contain a null character! + + return *text == character; +} + +bool String::endsWithChar (const beast_wchar character) const noexcept +{ + bassert (character != 0); // strings can't contain a null character! + + if (text.isEmpty()) + return false; + + CharPointerType t (text.findTerminatingNull()); + return *--t == character; +} + +bool String::endsWith (const String& other) const noexcept +{ + CharPointerType end (text.findTerminatingNull()); + CharPointerType otherEnd (other.text.findTerminatingNull()); + + while (end > text && otherEnd > other.text) + { + --end; + --otherEnd; + + if (*end != *otherEnd) + return false; + } + + return otherEnd == other.text; +} + +bool String::endsWithIgnoreCase (const String& other) const noexcept +{ + CharPointerType end (text.findTerminatingNull()); + CharPointerType otherEnd (other.text.findTerminatingNull()); + + while (end > text && otherEnd > other.text) + { + --end; + --otherEnd; + + if (end.toLowerCase() != otherEnd.toLowerCase()) + return false; + } + + return otherEnd == other.text; +} + +//============================================================================== +String String::toUpperCase() const +{ + StringCreationHelper builder (text); + + for (;;) + { + const beast_wchar c = builder.source.toUpperCase(); + ++(builder.source); + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::toLowerCase() const +{ + StringCreationHelper builder (text); + + for (;;) + { + const beast_wchar c = builder.source.toLowerCase(); + ++(builder.source); + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +//============================================================================== +beast_wchar String::getLastCharacter() const noexcept +{ + return isEmpty() ? beast_wchar() : text [length() - 1]; +} + +String String::substring (int start, const int end) const +{ + if (start < 0) + start = 0; + + if (end <= start) + return empty; + + int i = 0; + CharPointerType t1 (text); + + while (i < start) + { + if (t1.isEmpty()) + return empty; + + ++i; + ++t1; + } + + CharPointerType t2 (t1); + while (i < end) + { + if (t2.isEmpty()) + { + if (start == 0) + return *this; + + break; + } + + ++i; + ++t2; + } + + return String (t1, t2); +} + +String String::substring (int start) const +{ + if (start <= 0) + return *this; + + CharPointerType t (text); + + while (--start >= 0) + { + if (t.isEmpty()) + return empty; + + ++t; + } + + return String (t); +} + +String String::dropLastCharacters (const int numberToDrop) const +{ + return String (text, (size_t) bmax (0, length() - numberToDrop)); +} + +String String::getLastCharacters (const int numCharacters) const +{ + return String (text + bmax (0, length() - bmax (0, numCharacters))); +} + +String String::fromFirstOccurrenceOf (const String& sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? indexOfIgnoreCase (sub) + : indexOf (sub); + if (i < 0) + return empty; + + return substring (includeSubString ? i : i + sub.length()); +} + +String String::fromLastOccurrenceOf (const String& sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) + : lastIndexOf (sub); + if (i < 0) + return *this; + + return substring (includeSubString ? i : i + sub.length()); +} + +String String::upToFirstOccurrenceOf (const String& sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? indexOfIgnoreCase (sub) + : indexOf (sub); + if (i < 0) + return *this; + + return substring (0, includeSubString ? i + sub.length() : i); +} + +String String::upToLastOccurrenceOf (const String& sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) + : lastIndexOf (sub); + if (i < 0) + return *this; + + return substring (0, includeSubString ? i + sub.length() : i); +} + +bool String::isQuotedString() const +{ + const String trimmed (trimStart()); + + return trimmed[0] == '"' + || trimmed[0] == '\''; +} + +String String::unquoted() const +{ + const int len = length(); + + if (len == 0) + return empty; + + const beast_wchar lastChar = text [len - 1]; + const int dropAtStart = (*text == '"' || *text == '\'') ? 1 : 0; + const int dropAtEnd = (lastChar == '"' || lastChar == '\'') ? 1 : 0; + + return substring (dropAtStart, len - dropAtEnd); +} + +String String::quoted (const beast_wchar quoteCharacter) const +{ + if (isEmpty()) + return charToString (quoteCharacter) + quoteCharacter; + + String t (*this); + + if (! t.startsWithChar (quoteCharacter)) + t = charToString (quoteCharacter) + t; + + if (! t.endsWithChar (quoteCharacter)) + t += quoteCharacter; + + return t; +} + +//============================================================================== +static String::CharPointerType findTrimmedEnd (const String::CharPointerType& start, String::CharPointerType end) +{ + while (end > start) + { + if (! (--end).isWhitespace()) + { + ++end; + break; + } + } + + return end; +} + +String String::trim() const +{ + if (isNotEmpty()) + { + CharPointerType start (text.findEndOfWhitespace()); + + const CharPointerType end (start.findTerminatingNull()); + CharPointerType trimmedEnd (findTrimmedEnd (start, end)); + + if (trimmedEnd <= start) + return empty; + + if (text < start || trimmedEnd < end) + return String (start, trimmedEnd); + } + + return *this; +} + +String String::trimStart() const +{ + if (isNotEmpty()) + { + const CharPointerType t (text.findEndOfWhitespace()); + + if (t != text) + return String (t); + } + + return *this; +} + +String String::trimEnd() const +{ + if (isNotEmpty()) + { + const CharPointerType end (text.findTerminatingNull()); + CharPointerType trimmedEnd (findTrimmedEnd (text, end)); + + if (trimmedEnd < end) + return String (text, trimmedEnd); + } + + return *this; +} + +String String::trimCharactersAtStart (const String& charactersToTrim) const +{ + CharPointerType t (text); + + while (charactersToTrim.containsChar (*t)) + ++t; + + return t == text ? *this : String (t); +} + +String String::trimCharactersAtEnd (const String& charactersToTrim) const +{ + if (isNotEmpty()) + { + const CharPointerType end (text.findTerminatingNull()); + CharPointerType trimmedEnd (end); + + while (trimmedEnd > text) + { + if (! charactersToTrim.containsChar (*--trimmedEnd)) + { + ++trimmedEnd; + break; + } + } + + if (trimmedEnd < end) + return String (text, trimmedEnd); + } + + return *this; +} + +//============================================================================== +String String::retainCharacters (const String& charactersToRetain) const +{ + if (isEmpty()) + return empty; + + StringCreationHelper builder (text); + + for (;;) + { + beast_wchar c = builder.source.getAndAdvance(); + + if (charactersToRetain.containsChar (c)) + builder.write (c); + + if (c == 0) + break; + } + + builder.write (0); + return builder.result; +} + +String String::removeCharacters (const String& charactersToRemove) const +{ + if (isEmpty()) + return empty; + + StringCreationHelper builder (text); + + for (;;) + { + beast_wchar c = builder.source.getAndAdvance(); + + if (! charactersToRemove.containsChar (c)) + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::initialSectionContainingOnly (const String& permittedCharacters) const +{ + CharPointerType t (text); + + while (! t.isEmpty()) + { + if (! permittedCharacters.containsChar (*t)) + return String (text, t); + + ++t; + } + + return *this; +} + +String String::initialSectionNotContaining (const String& charactersToStopAt) const +{ + CharPointerType t (text); + + while (! t.isEmpty()) + { + if (charactersToStopAt.containsChar (*t)) + return String (text, t); + + ++t; + } + + return *this; +} + +bool String::containsOnly (const String& chars) const noexcept +{ + CharPointerType t (text); + + while (! t.isEmpty()) + if (! chars.containsChar (t.getAndAdvance())) + return false; + + return true; +} + +bool String::containsAnyOf (const String& chars) const noexcept +{ + CharPointerType t (text); + + while (! t.isEmpty()) + if (chars.containsChar (t.getAndAdvance())) + return true; + + return false; +} + +bool String::containsNonWhitespaceChars() const noexcept +{ + CharPointerType t (text); + + while (! t.isEmpty()) + { + if (! t.isWhitespace()) + return true; + + ++t; + } + + return false; +} + +// Note! The format parameter here MUST NOT be a reference, otherwise MS's va_start macro fails to work (but still compiles). +String String::formatted (const String pf, ... ) +{ + size_t bufferSize = 256; + + for (;;) + { + va_list args; + va_start (args, pf); + + #if BEAST_WINDOWS + HeapBlock temp (bufferSize); + const int num = (int) _vsnwprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); + #elif BEAST_ANDROID + HeapBlock temp (bufferSize); + const int num = (int) vsnprintf (temp.getData(), bufferSize - 1, pf.toUTF8(), args); + #else + HeapBlock temp (bufferSize); + const int num = (int) vswprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); + #endif + + va_end (args); + + if (num > 0) + return String (temp); + + bufferSize += 256; + + if (num == 0 || bufferSize > 65536) // the upper limit is a sanity check to avoid situations where vprintf repeatedly + break; // returns -1 because of an error rather than because it needs more space. + } + + return empty; +} + +//============================================================================== +int String::getIntValue() const noexcept +{ + return text.getIntValue32(); +} + +int String::getTrailingIntValue() const noexcept +{ + int n = 0; + int mult = 1; + CharPointerType t (text.findTerminatingNull()); + + while (--t >= text) + { + if (! t.isDigit()) + { + if (*t == '-') + n = -n; + + break; + } + + n += mult * (*t - '0'); + mult *= 10; + } + + return n; +} + +int64 String::getLargeIntValue() const noexcept +{ + return text.getIntValue64(); +} + +float String::getFloatValue() const noexcept +{ + return (float) getDoubleValue(); +} + +double String::getDoubleValue() const noexcept +{ + return text.getDoubleValue(); +} + +static const char hexDigits[] = "0123456789abcdef"; + +template +struct HexConverter +{ + static String hexToString (Type v) + { + char buffer[32]; + char* const end = buffer + 32; + char* t = end; + *--t = 0; + + do + { + *--t = hexDigits [(int) (v & 15)]; + v >>= 4; + + } while (v != 0); + + return String (t, (size_t) (end - t) - 1); + } + + static Type stringToHex (String::CharPointerType t) noexcept + { + Type result = 0; + + while (! t.isEmpty()) + { + const int hexValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); + + if (hexValue >= 0) + result = (result << 4) | hexValue; + } + + return result; + } +}; + +String String::toHexString (const int number) +{ + return HexConverter ::hexToString ((unsigned int) number); +} + +String String::toHexString (const int64 number) +{ + return HexConverter ::hexToString ((uint64) number); +} + +String String::toHexString (const short number) +{ + return toHexString ((int) (unsigned short) number); +} + +String String::toHexString (const void* const d, const int size, const int groupSize) +{ + if (size <= 0) + return empty; + + int numChars = (size * 2) + 2; + if (groupSize > 0) + numChars += size / groupSize; + + String s (PreallocationBytes (sizeof (CharPointerType::CharType) * (size_t) numChars)); + + const unsigned char* data = static_cast (d); + CharPointerType dest (s.text); + + for (int i = 0; i < size; ++i) + { + const unsigned char nextByte = *data++; + dest.write ((beast_wchar) hexDigits [nextByte >> 4]); + dest.write ((beast_wchar) hexDigits [nextByte & 0xf]); + + if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) + dest.write ((beast_wchar) ' '); + } + + dest.writeNull(); + return s; +} + +int String::getHexValue32() const noexcept { return HexConverter ::stringToHex (text); } +int64 String::getHexValue64() const noexcept { return HexConverter::stringToHex (text); } + +//============================================================================== +String String::createStringFromData (const void* const data_, const int size) +{ + const uint8* const data = static_cast (data_); + + if (size <= 0 || data == nullptr) + return empty; + + if (size == 1) + return charToString ((beast_wchar) data[0]); + + if ((data[0] == (uint8) CharPointer_UTF16::byteOrderMarkBE1 && data[1] == (uint8) CharPointer_UTF16::byteOrderMarkBE2) + || (data[0] == (uint8) CharPointer_UTF16::byteOrderMarkLE1 && data[1] == (uint8) CharPointer_UTF16::byteOrderMarkLE2)) + { + const bool bigEndian = (data[0] == (uint8) CharPointer_UTF16::byteOrderMarkBE1); + const int numChars = size / 2 - 1; + + StringCreationHelper builder ((size_t) numChars); + + const uint16* const src = (const uint16*) (data + 2); + + if (bigEndian) + { + for (int i = 0; i < numChars; ++i) + builder.write ((beast_wchar) ByteOrder::swapIfLittleEndian (src[i])); + } + else + { + for (int i = 0; i < numChars; ++i) + builder.write ((beast_wchar) ByteOrder::swapIfBigEndian (src[i])); + } + + builder.write (0); + return builder.result; + } + + const uint8* start = data; + const uint8* end = data + size; + + if (size >= 3 + && data[0] == (uint8) CharPointer_UTF8::byteOrderMark1 + && data[1] == (uint8) CharPointer_UTF8::byteOrderMark2 + && data[2] == (uint8) CharPointer_UTF8::byteOrderMark3) + start += 3; + + return String (CharPointer_UTF8 ((const char*) start), + CharPointer_UTF8 ((const char*) end)); +} + +//============================================================================== +static const beast_wchar emptyChar = 0; + +template +struct StringEncodingConverter +{ + static CharPointerType_Dest convert (const String& s) + { + String& source = const_cast (s); + + typedef typename CharPointerType_Dest::CharType DestChar; + + if (source.isEmpty()) + return CharPointerType_Dest (reinterpret_cast (&emptyChar)); + + CharPointerType_Src text (source.getCharPointer()); + const size_t extraBytesNeeded = CharPointerType_Dest::getBytesRequiredFor (text); + const size_t endOffset = (text.sizeInBytes() + 3) & ~3u; // the new string must be word-aligned or many Windows + // functions will fail to read it correctly! + source.preallocateBytes (endOffset + extraBytesNeeded); + text = source.getCharPointer(); + + void* const newSpace = addBytesToPointer (text.getAddress(), (int) endOffset); + const CharPointerType_Dest extraSpace (static_cast (newSpace)); + + #if BEAST_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) + const size_t bytesToClear = (size_t) bmin ((int) extraBytesNeeded, 4); + zeromem (addBytesToPointer (newSpace, extraBytesNeeded - bytesToClear), bytesToClear); + #endif + + CharPointerType_Dest (extraSpace).writeAll (text); + return extraSpace; + } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 ((CharPointer_UTF8::CharType*) source.getCharPointer().getAddress()); } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 ((CharPointer_UTF16::CharType*) source.getCharPointer().getAddress()); } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 ((CharPointer_UTF32::CharType*) source.getCharPointer().getAddress()); } +}; + +CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter ::convert (*this); } +CharPointer_UTF16 String::toUTF16() const { return StringEncodingConverter ::convert (*this); } +CharPointer_UTF32 String::toUTF32() const { return StringEncodingConverter ::convert (*this); } + +const char* String::toRawUTF8() const +{ + return toUTF8().getAddress(); +} + +const wchar_t* String::toWideCharPointer() const +{ + return StringEncodingConverter ::convert (*this).getAddress(); +} + +std::string String::toStdString() const +{ + return std::string (toRawUTF8()); +} + +//============================================================================== +template +struct StringCopier +{ + static size_t copyToBuffer (const CharPointerType_Src source, typename CharPointerType_Dest::CharType* const buffer, const size_t maxBufferSizeBytes) + { + bassert (((ssize_t) maxBufferSizeBytes) >= 0); // keep this value positive! + + if (buffer == nullptr) + return CharPointerType_Dest::getBytesRequiredFor (source) + sizeof (typename CharPointerType_Dest::CharType); + + return CharPointerType_Dest (buffer).writeWithDestByteLimit (source, maxBufferSizeBytes); + } +}; + +size_t String::copyToUTF8 (CharPointer_UTF8::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier ::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +size_t String::copyToUTF16 (CharPointer_UTF16::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier ::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +size_t String::copyToUTF32 (CharPointer_UTF32::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier ::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +//============================================================================== +size_t String::getNumBytesAsUTF8() const noexcept +{ + return CharPointer_UTF8::getBytesRequiredFor (text); +} + +String String::fromUTF8 (const char* const buffer, int bufferSizeBytes) +{ + if (buffer != nullptr) + { + if (bufferSizeBytes < 0) return String (CharPointer_UTF8 (buffer)); + if (bufferSizeBytes > 0) return String (CharPointer_UTF8 (buffer), + CharPointer_UTF8 (buffer + bufferSizeBytes)); + } + + return String::empty; +} + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +//============================================================================== +#if BEAST_UNIT_TESTS + +class StringTests : public UnitTest +{ +public: + StringTests() : UnitTest ("String class") {} + + template + struct TestUTFConversion + { + static void test (UnitTest& test) + { + String s (createRandomWideCharString()); + + typename CharPointerType::CharType buffer [300]; + + memset (buffer, 0xff, sizeof (buffer)); + CharPointerType (buffer).writeAll (s.toUTF32()); + test.expectEquals (String (CharPointerType (buffer)), s); + + memset (buffer, 0xff, sizeof (buffer)); + CharPointerType (buffer).writeAll (s.toUTF16()); + test.expectEquals (String (CharPointerType (buffer)), s); + + memset (buffer, 0xff, sizeof (buffer)); + CharPointerType (buffer).writeAll (s.toUTF8()); + test.expectEquals (String (CharPointerType (buffer)), s); + + test.expect (CharPointerType::isValidString (buffer, (int) strlen ((const char*) buffer))); + } + }; + + static String createRandomWideCharString() + { + beast_wchar buffer[50] = { 0 }; + Random r; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (beast_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (beast_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } + + void runTest() + { + { + beginTest ("Basics"); + + expect (String().length() == 0); + expect (String() == String::empty); + String s1, s2 ("abcd"); + expect (s1.isEmpty() && ! s1.isNotEmpty()); + expect (s2.isNotEmpty() && ! s2.isEmpty()); + expect (s2.length() == 4); + s1 = "abcd"; + expect (s2 == s1 && s1 == s2); + expect (s1 == "abcd" && s1 == L"abcd"); + expect (String ("abcd") == String (L"abcd")); + expect (String ("abcdefg", 4) == L"abcd"); + expect (String ("abcdefg", 4) == String (L"abcdefg", 4)); + expect (String::charToString ('x') == "x"); + expect (String::charToString (0) == String::empty); + expect (s2 + "e" == "abcde" && s2 + 'e' == "abcde"); + expect (s2 + L'e' == "abcde" && s2 + L"e" == "abcde"); + expect (s1.equalsIgnoreCase ("abcD") && s1 < "abce" && s1 > "abbb"); + expect (s1.startsWith ("ab") && s1.startsWith ("abcd") && ! s1.startsWith ("abcde")); + expect (s1.startsWithIgnoreCase ("aB") && s1.endsWithIgnoreCase ("CD")); + expect (s1.endsWith ("bcd") && ! s1.endsWith ("aabcd")); + expectEquals (s1.indexOf (String::empty), 0); + expectEquals (s1.indexOfIgnoreCase (String::empty), 0); + expect (s1.startsWith (String::empty) && s1.endsWith (String::empty) && s1.contains (String::empty)); + expect (s1.contains ("cd") && s1.contains ("ab") && s1.contains ("abcd")); + expect (s1.containsChar ('a')); + expect (! s1.containsChar ('x')); + expect (! s1.containsChar (0)); + expect (String ("abc foo bar").containsWholeWord ("abc") && String ("abc foo bar").containsWholeWord ("abc")); + } + + { + beginTest ("Operations"); + + String s ("012345678"); + expect (s.hashCode() != 0); + expect (s.hashCode64() != 0); + expect (s.hashCode() != (s + s).hashCode()); + expect (s.hashCode64() != (s + s).hashCode64()); + expect (s.compare (String ("012345678")) == 0); + expect (s.compare (String ("012345679")) < 0); + expect (s.compare (String ("012345676")) > 0); + expect (s.substring (2, 3) == String::charToString (s[2])); + expect (s.substring (0, 1) == String::charToString (s[0])); + expect (s.getLastCharacter() == s [s.length() - 1]); + expect (String::charToString (s.getLastCharacter()) == s.getLastCharacters (1)); + expect (s.substring (0, 3) == L"012"); + expect (s.substring (0, 100) == s); + expect (s.substring (-1, 100) == s); + expect (s.substring (3) == "345678"); + expect (s.indexOf (L"45") == 4); + expect (String ("444445").indexOf ("45") == 4); + expect (String ("444445").lastIndexOfChar ('4') == 4); + expect (String ("45454545x").lastIndexOf (L"45") == 6); + expect (String ("45454545x").lastIndexOfAnyOf ("456") == 7); + expect (String ("45454545x").lastIndexOfAnyOf (L"456x") == 8); + expect (String ("abABaBaBa").lastIndexOfIgnoreCase ("aB") == 6); + expect (s.indexOfChar (L'4') == 4); + expect (s + s == "012345678012345678"); + expect (s.startsWith (s)); + expect (s.startsWith (s.substring (0, 4))); + expect (s.startsWith (s.dropLastCharacters (4))); + expect (s.endsWith (s.substring (5))); + expect (s.endsWith (s)); + expect (s.contains (s.substring (3, 6))); + expect (s.contains (s.substring (3))); + expect (s.startsWithChar (s[0])); + expect (s.endsWithChar (s.getLastCharacter())); + expect (s [s.length()] == 0); + expect (String ("abcdEFGH").toLowerCase() == String ("abcdefgh")); + expect (String ("abcdEFGH").toUpperCase() == String ("ABCDEFGH")); + + String s2 ("123"); + s2 << ((int) 4) << ((short) 5) << "678" << L"9" << '0'; + s2 += "xyz"; + expect (s2 == "1234567890xyz"); + + beginTest ("Numeric conversions"); + expect (String::empty.getIntValue() == 0); + expect (String::empty.getDoubleValue() == 0.0); + expect (String::empty.getFloatValue() == 0.0f); + expect (s.getIntValue() == 12345678); + expect (s.getLargeIntValue() == (int64) 12345678); + expect (s.getDoubleValue() == 12345678.0); + expect (s.getFloatValue() == 12345678.0f); + expect (String (-1234).getIntValue() == -1234); + expect (String ((int64) -1234).getLargeIntValue() == -1234); + expect (String (-1234.56).getDoubleValue() == -1234.56); + expect (String (-1234.56f).getFloatValue() == -1234.56f); + expect (("xyz" + s).getTrailingIntValue() == s.getIntValue()); + expect (s.getHexValue32() == 0x12345678); + expect (s.getHexValue64() == (int64) 0x12345678); + expect (String::toHexString (0x1234abcd).equalsIgnoreCase ("1234abcd")); + expect (String::toHexString ((int64) 0x1234abcd).equalsIgnoreCase ("1234abcd")); + expect (String::toHexString ((short) 0x12ab).equalsIgnoreCase ("12ab")); + + unsigned char data[] = { 1, 2, 3, 4, 0xa, 0xb, 0xc, 0xd }; + expect (String::toHexString (data, 8, 0).equalsIgnoreCase ("010203040a0b0c0d")); + expect (String::toHexString (data, 8, 1).equalsIgnoreCase ("01 02 03 04 0a 0b 0c 0d")); + expect (String::toHexString (data, 8, 2).equalsIgnoreCase ("0102 0304 0a0b 0c0d")); + + beginTest ("Subsections"); + String s3; + s3 = "abcdeFGHIJ"; + expect (s3.equalsIgnoreCase ("ABCdeFGhiJ")); + expect (s3.compareIgnoreCase (L"ABCdeFGhiJ") == 0); + expect (s3.containsIgnoreCase (s3.substring (3))); + expect (s3.indexOfAnyOf ("xyzf", 2, true) == 5); + expect (s3.indexOfAnyOf (L"xyzf", 2, false) == -1); + expect (s3.indexOfAnyOf ("xyzF", 2, false) == 5); + expect (s3.containsAnyOf (L"zzzFs")); + expect (s3.startsWith ("abcd")); + expect (s3.startsWithIgnoreCase (L"abCD")); + expect (s3.startsWith (String::empty)); + expect (s3.startsWithChar ('a')); + expect (s3.endsWith (String ("HIJ"))); + expect (s3.endsWithIgnoreCase (L"Hij")); + expect (s3.endsWith (String::empty)); + expect (s3.endsWithChar (L'J')); + expect (s3.indexOf ("HIJ") == 7); + expect (s3.indexOf (L"HIJK") == -1); + expect (s3.indexOfIgnoreCase ("hij") == 7); + expect (s3.indexOfIgnoreCase (L"hijk") == -1); + expect (s3.toStdString() == s3.toRawUTF8()); + + String s4 (s3); + s4.append (String ("xyz123"), 3); + expect (s4 == s3 + "xyz"); + + expect (String (1234) < String (1235)); + expect (String (1235) > String (1234)); + expect (String (1234) >= String (1234)); + expect (String (1234) <= String (1234)); + expect (String (1235) >= String (1234)); + expect (String (1234) <= String (1235)); + + String s5 ("word word2 word3"); + expect (s5.containsWholeWord (String ("word2"))); + expect (s5.indexOfWholeWord ("word2") == 5); + expect (s5.containsWholeWord (L"word")); + expect (s5.containsWholeWord ("word3")); + expect (s5.containsWholeWord (s5)); + expect (s5.containsWholeWordIgnoreCase (L"Word2")); + expect (s5.indexOfWholeWordIgnoreCase ("Word2") == 5); + expect (s5.containsWholeWordIgnoreCase (L"Word")); + expect (s5.containsWholeWordIgnoreCase ("Word3")); + expect (! s5.containsWholeWordIgnoreCase (L"Wordx")); + expect (! s5.containsWholeWordIgnoreCase ("xWord2")); + expect (s5.containsNonWhitespaceChars()); + expect (s5.containsOnly ("ordw23 ")); + expect (! String (" \n\r\t").containsNonWhitespaceChars()); + + expect (s5.matchesWildcard (L"wor*", false)); + expect (s5.matchesWildcard ("wOr*", true)); + expect (s5.matchesWildcard (L"*word3", true)); + expect (s5.matchesWildcard ("*word?", true)); + expect (s5.matchesWildcard (L"Word*3", true)); + expect (! s5.matchesWildcard (L"*34", true)); + expect (String ("xx**y").matchesWildcard ("*y", true)); + expect (String ("xx**y").matchesWildcard ("x*y", true)); + expect (String ("xx**y").matchesWildcard ("xx*y", true)); + expect (String ("xx**y").matchesWildcard ("xx*", true)); + expect (String ("xx?y").matchesWildcard ("x??y", true)); + expect (String ("xx?y").matchesWildcard ("xx?y", true)); + expect (! String ("xx?y").matchesWildcard ("xx?y?", true)); + expect (String ("xx?y").matchesWildcard ("xx??", true)); + + expectEquals (s5.fromFirstOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.fromFirstOccurrenceOf ("xword2", true, false), s5.substring (100)); + expectEquals (s5.fromFirstOccurrenceOf (L"word2", true, false), s5.substring (5)); + expectEquals (s5.fromFirstOccurrenceOf ("Word2", true, true), s5.substring (5)); + expectEquals (s5.fromFirstOccurrenceOf ("word2", false, false), s5.getLastCharacters (6)); + expectEquals (s5.fromFirstOccurrenceOf (L"Word2", false, true), s5.getLastCharacters (6)); + + expectEquals (s5.fromLastOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.fromLastOccurrenceOf (L"wordx", true, false), s5); + expectEquals (s5.fromLastOccurrenceOf ("word", true, false), s5.getLastCharacters (5)); + expectEquals (s5.fromLastOccurrenceOf (L"worD", true, true), s5.getLastCharacters (5)); + expectEquals (s5.fromLastOccurrenceOf ("word", false, false), s5.getLastCharacters (1)); + expectEquals (s5.fromLastOccurrenceOf (L"worD", false, true), s5.getLastCharacters (1)); + + expect (s5.upToFirstOccurrenceOf (String::empty, true, false).isEmpty()); + expectEquals (s5.upToFirstOccurrenceOf ("word4", true, false), s5); + expectEquals (s5.upToFirstOccurrenceOf (L"word2", true, false), s5.substring (0, 10)); + expectEquals (s5.upToFirstOccurrenceOf ("Word2", true, true), s5.substring (0, 10)); + expectEquals (s5.upToFirstOccurrenceOf (L"word2", false, false), s5.substring (0, 5)); + expectEquals (s5.upToFirstOccurrenceOf ("Word2", false, true), s5.substring (0, 5)); + + expectEquals (s5.upToLastOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.upToLastOccurrenceOf ("zword", true, false), s5); + expectEquals (s5.upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); + expectEquals (s5.dropLastCharacters(1).upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); + expectEquals (s5.upToLastOccurrenceOf ("Word", true, true), s5.dropLastCharacters (1)); + expectEquals (s5.upToLastOccurrenceOf ("word", false, false), s5.dropLastCharacters (5)); + expectEquals (s5.upToLastOccurrenceOf ("Word", false, true), s5.dropLastCharacters (5)); + + expectEquals (s5.replace ("word", L"xyz", false), String ("xyz xyz2 xyz3")); + expect (s5.replace (L"Word", "xyz", true) == "xyz xyz2 xyz3"); + expect (s5.dropLastCharacters (1).replace ("Word", String ("xyz"), true) == L"xyz xyz2 xyz"); + expect (s5.replace ("Word", "", true) == " 2 3"); + expectEquals (s5.replace ("Word2", L"xyz", true), String ("word xyz word3")); + expect (s5.replaceCharacter (L'w', 'x') != s5); + expectEquals (s5.replaceCharacter ('w', L'x').replaceCharacter ('x', 'w'), s5); + expect (s5.replaceCharacters ("wo", "xy") != s5); + expectEquals (s5.replaceCharacters ("wo", "xy").replaceCharacters ("xy", L"wo"), s5); + expectEquals (s5.retainCharacters ("1wordxya"), String ("wordwordword")); + expect (s5.retainCharacters (String::empty).isEmpty()); + expect (s5.removeCharacters ("1wordxya") == " 2 3"); + expectEquals (s5.removeCharacters (String::empty), s5); + expect (s5.initialSectionContainingOnly ("word") == L"word"); + expect (String ("word").initialSectionContainingOnly ("word") == L"word"); + expectEquals (s5.initialSectionNotContaining (String ("xyz ")), String ("word")); + expectEquals (s5.initialSectionNotContaining (String (";[:'/")), s5); + expect (! s5.isQuotedString()); + expect (s5.quoted().isQuotedString()); + expect (! s5.quoted().unquoted().isQuotedString()); + expect (! String ("x'").isQuotedString()); + expect (String ("'x").isQuotedString()); + + String s6 (" \t xyz \t\r\n"); + expectEquals (s6.trim(), String ("xyz")); + expect (s6.trim().trim() == "xyz"); + expectEquals (s5.trim(), s5); + expectEquals (s6.trimStart().trimEnd(), s6.trim()); + expectEquals (s6.trimStart().trimEnd(), s6.trimEnd().trimStart()); + expectEquals (s6.trimStart().trimStart().trimEnd().trimEnd(), s6.trimEnd().trimStart()); + expect (s6.trimStart() != s6.trimEnd()); + expectEquals (("\t\r\n " + s6 + "\t\n \r").trim(), s6.trim()); + expect (String::repeatedString ("xyz", 3) == L"xyzxyzxyz"); + } + + { + beginTest ("UTF conversions"); + + TestUTFConversion ::test (*this); + TestUTFConversion ::test (*this); + TestUTFConversion ::test (*this); + } + + { + beginTest ("StringArray"); + + StringArray s; + s.addTokens ("4,3,2,1,0", ";,", "x"); + expectEquals (s.size(), 5); + + expectEquals (s.joinIntoString ("-"), String ("4-3-2-1-0")); + s.remove (2); + expectEquals (s.joinIntoString ("--"), String ("4--3--1--0")); + expectEquals (s.joinIntoString (String::empty), String ("4310")); + s.clear(); + expectEquals (s.joinIntoString ("x"), String::empty); + + StringArray toks; + toks.addTokens ("x,,", ";,", ""); + expectEquals (toks.size(), 3); + expectEquals (toks.joinIntoString ("-"), String ("x--")); + toks.clear(); + + toks.addTokens (",x,", ";,", ""); + expectEquals (toks.size(), 3); + expectEquals (toks.joinIntoString ("-"), String ("-x-")); + toks.clear(); + + toks.addTokens ("x,'y,z',", ";,", "'"); + expectEquals (toks.size(), 3); + expectEquals (toks.joinIntoString ("-"), String ("x-'y,z'-")); + } + } +}; + +static StringTests stringUnitTests; + +#endif diff --git a/modules/beast_core/text/beast_String.h b/modules/beast_core/text/beast_String.h new file mode 100644 index 0000000000..3a4ba8df9a --- /dev/null +++ b/modules/beast_core/text/beast_String.h @@ -0,0 +1,1346 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_STRING_BEASTHEADER +#define BEAST_STRING_BEASTHEADER + +#include "beast_CharacterFunctions.h" + +#ifndef BEAST_STRING_UTF_TYPE + #define BEAST_STRING_UTF_TYPE 8 +#endif + +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +#include "../memory/beast_Atomic.h" +#include "beast_CharPointer_UTF8.h" +#include "beast_CharPointer_UTF16.h" +#include "beast_CharPointer_UTF32.h" +#include "beast_CharPointer_ASCII.h" + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +class OutputStream; + +//============================================================================== +/** + The BEAST String class! + + Using a reference-counted internal representation, these strings are fast + and efficient, and there are methods to do just about any operation you'll ever + dream of. + + @see StringArray, StringPairArray +*/ +class BEAST_API String +{ +public: + //============================================================================== + /** Creates an empty string. + @see empty + */ + String() noexcept; + + /** Creates a copy of another string. */ + String (const String& other) noexcept; + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + String (String&& other) noexcept; + #endif + + /** Creates a string from a zero-terminated ascii text string. + + The string passed-in must not contain any characters with a value above 127, because + these can't be converted to unicode without knowing the original encoding that was + used to create the string. If you attempt to pass-in values above 127, you'll get an + assertion. + + To create strings with extended characters from UTF-8, you should explicitly call + String (CharPointer_UTF8 ("my utf8 string..")). It's *highly* recommended that you + use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent unicode strings in a way that isn't dependent + on the compiler, source code editor and platform. + */ + String (const char* text); + + /** Creates a string from a string of 8-bit ascii characters. + + The string passed-in must not contain any characters with a value above 127, because + these can't be converted to unicode without knowing the original encoding that was + used to create the string. If you attempt to pass-in values above 127, you'll get an + assertion. + + To create strings with extended characters from UTF-8, you should explicitly call + String (CharPointer_UTF8 ("my utf8 string..")). It's *highly* recommended that you + use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent unicode strings in a way that isn't dependent + on the compiler, source code editor and platform. + + This will use up the the first maxChars characters of the string (or less if the string + is actually shorter). + */ + String (const char* text, size_t maxChars); + + /** Creates a string from a whcar_t character string. + Depending on the platform, this may be treated as either UTF-32 or UTF-16. + */ + String (const wchar_t* text); + + /** Creates a string from a whcar_t character string. + Depending on the platform, this may be treated as either UTF-32 or UTF-16. + */ + String (const wchar_t* text, size_t maxChars); + + //============================================================================== + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 text); + + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 text, size_t maxChars); + + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 start, const CharPointer_UTF8 end); + + //============================================================================== + /** Creates a string from a UTF-16 character string */ + String (const CharPointer_UTF16 text); + + /** Creates a string from a UTF-16 character string */ + String (const CharPointer_UTF16 text, size_t maxChars); + + /** Creates a string from a UTF-16 character string */ + String (const CharPointer_UTF16 start, const CharPointer_UTF16 end); + + //============================================================================== + /** Creates a string from a UTF-32 character string */ + String (const CharPointer_UTF32 text); + + /** Creates a string from a UTF-32 character string */ + String (const CharPointer_UTF32 text, size_t maxChars); + + /** Creates a string from a UTF-32 character string */ + String (const CharPointer_UTF32 start, const CharPointer_UTF32 end); + + //============================================================================== + /** Creates a string from an ASCII character string */ + String (const CharPointer_ASCII text); + + /** Creates a string from a UTF-8 encoded std::string. */ + String (const std::string&); + + //============================================================================== + /** Creates a string from a single character. */ + static String charToString (beast_wchar character); + + /** Destructor. */ + ~String() noexcept; + + //============================================================================== + /** This is an empty string that can be used whenever one is needed. + + It's better to use this than String() because it explains what's going on + and is more efficient. + */ + static const String empty; + + /** This is the character encoding type used internally to store the string. + + By setting the value of BEAST_STRING_UTF_TYPE to 8, 16, or 32, you can change the + internal storage format of the String class. UTF-8 uses the least space (if your strings + contain few extended characters), but call operator[] involves iterating the string to find + the required index. UTF-32 provides instant random access to its characters, but uses 4 bytes + per character to store them. UTF-16 uses more space than UTF-8 and is also slow to index, + but is the native wchar_t format used in Windows. + + It doesn't matter too much which format you pick, because the toUTF8(), toUTF16() and + toUTF32() methods let you access the string's content in any of the other formats. + */ + #if (BEAST_STRING_UTF_TYPE == 32) + typedef CharPointer_UTF32 CharPointerType; + #elif (BEAST_STRING_UTF_TYPE == 16) + typedef CharPointer_UTF16 CharPointerType; + #elif (BEAST_STRING_UTF_TYPE == 8) + typedef CharPointer_UTF8 CharPointerType; + #else + #error "You must set the value of BEAST_STRING_UTF_TYPE to be either 8, 16, or 32!" + #endif + + //============================================================================== + /** Generates a probably-unique 32-bit hashcode from this string. */ + int hashCode() const noexcept; + + /** Generates a probably-unique 64-bit hashcode from this string. */ + int64 hashCode64() const noexcept; + + /** Returns the number of characters in the string. */ + int length() const noexcept; + + //============================================================================== + // Assignment and concatenation operators.. + + /** Replaces this string's contents with another string. */ + String& operator= (const String& other) noexcept; + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + String& operator= (String&& other) noexcept; + #endif + + /** Appends another string at the end of this one. */ + String& operator+= (const String& stringToAppend); + /** Appends another string at the end of this one. */ + String& operator+= (const char* textToAppend); + /** Appends another string at the end of this one. */ + String& operator+= (const wchar_t* textToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (int numberToAppend); + /** Appends a character at the end of this string. */ + String& operator+= (char characterToAppend); + /** Appends a character at the end of this string. */ + String& operator+= (wchar_t characterToAppend); + #if ! BEAST_NATIVE_WCHAR_IS_UTF32 + /** Appends a character at the end of this string. */ + String& operator+= (beast_wchar characterToAppend); + #endif + + /** Appends a string to the end of this one. + + @param textToAppend the string to add + @param maxCharsToTake the maximum number of characters to take from the string passed in + */ + void append (const String& textToAppend, size_t maxCharsToTake); + + /** Appends a string to the end of this one. + + @param textToAppend the string to add + @param maxCharsToTake the maximum number of characters to take from the string passed in + */ + template + void appendCharPointer (const CharPointer textToAppend, size_t maxCharsToTake) + { + if (textToAppend.getAddress() != nullptr) + { + size_t extraBytesNeeded = 0; + size_t numChars = 0; + + for (CharPointer t (textToAppend); numChars < maxCharsToTake && ! t.isEmpty();) + { + extraBytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + ++numChars; + } + + if (numChars > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + + preallocateBytes (byteOffsetOfNull + extraBytesNeeded); + CharPointerType (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)).writeWithCharLimit (textToAppend, (int) (numChars + 1)); + } + } + } + + /** Appends a string to the end of this one. */ + template + void appendCharPointer (const CharPointer textToAppend) + { + if (textToAppend.getAddress() != nullptr) + { + size_t extraBytesNeeded = 0; + + for (CharPointer t (textToAppend); ! t.isEmpty();) + extraBytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + + preallocateBytes (byteOffsetOfNull + extraBytesNeeded); + CharPointerType (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)).writeAll (textToAppend); + } + } + } + + //============================================================================== + // Comparison methods.. + + /** Returns true if the string contains no characters. + Note that there's also an isNotEmpty() method to help write readable code. + @see containsNonWhitespaceChars() + */ + inline bool isEmpty() const noexcept { return text[0] == 0; } + + /** Returns true if the string contains at least one character. + Note that there's also an isEmpty() method to help write readable code. + @see containsNonWhitespaceChars() + */ + inline bool isNotEmpty() const noexcept { return text[0] != 0; } + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const String& other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const wchar_t* other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const char* other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const String& other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const char* other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const wchar_t* other) const noexcept; + + /** Case-insensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compareIgnoreCase (const String& other) const noexcept; + + /** Lexicographic comparison with another string. + + The comparison used here is case-insensitive and ignores leading non-alphanumeric + characters, making it good for sorting human-readable strings. + + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compareLexicographically (const String& other) const noexcept; + + /** Tests whether the string begins with another string. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool startsWith (const String& text) const noexcept; + + /** Tests whether the string begins with a particular character. + If the character is 0, this will always return false. + Uses a case-sensitive comparison. + */ + bool startsWithChar (beast_wchar character) const noexcept; + + /** Tests whether the string begins with another string. + If the parameter is an empty string, this will always return true. + Uses a case-insensitive comparison. + */ + bool startsWithIgnoreCase (const String& text) const noexcept; + + /** Tests whether the string ends with another string. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool endsWith (const String& text) const noexcept; + + /** Tests whether the string ends with a particular character. + If the character is 0, this will always return false. + Uses a case-sensitive comparison. + */ + bool endsWithChar (beast_wchar character) const noexcept; + + /** Tests whether the string ends with another string. + If the parameter is an empty string, this will always return true. + Uses a case-insensitive comparison. + */ + bool endsWithIgnoreCase (const String& text) const noexcept; + + /** Tests whether the string contains another substring. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool contains (const String& text) const noexcept; + + /** Tests whether the string contains a particular character. + Uses a case-sensitive comparison. + */ + bool containsChar (beast_wchar character) const noexcept; + + /** Tests whether the string contains another substring. + Uses a case-insensitive comparison. + */ + bool containsIgnoreCase (const String& text) const noexcept; + + /** Tests whether the string contains another substring as a distinct word. + + @returns true if the string contains this word, surrounded by + non-alphanumeric characters + @see indexOfWholeWord, containsWholeWordIgnoreCase + */ + bool containsWholeWord (const String& wordToLookFor) const noexcept; + + /** Tests whether the string contains another substring as a distinct word. + + @returns true if the string contains this word, surrounded by + non-alphanumeric characters + @see indexOfWholeWordIgnoreCase, containsWholeWord + */ + bool containsWholeWordIgnoreCase (const String& wordToLookFor) const noexcept; + + /** Finds an instance of another substring if it exists as a distinct word. + + @returns if the string contains this word, surrounded by non-alphanumeric characters, + then this will return the index of the start of the substring. If it isn't + found, then it will return -1 + @see indexOfWholeWordIgnoreCase, containsWholeWord + */ + int indexOfWholeWord (const String& wordToLookFor) const noexcept; + + /** Finds an instance of another substring if it exists as a distinct word. + + @returns if the string contains this word, surrounded by non-alphanumeric characters, + then this will return the index of the start of the substring. If it isn't + found, then it will return -1 + @see indexOfWholeWord, containsWholeWordIgnoreCase + */ + int indexOfWholeWordIgnoreCase (const String& wordToLookFor) const noexcept; + + /** Looks for any of a set of characters in the string. + Uses a case-sensitive comparison. + + @returns true if the string contains any of the characters from + the string that is passed in. + */ + bool containsAnyOf (const String& charactersItMightContain) const noexcept; + + /** Looks for a set of characters in the string. + Uses a case-sensitive comparison. + + @returns Returns false if any of the characters in this string do not occur in + the parameter string. If this string is empty, the return value will + always be true. + */ + bool containsOnly (const String& charactersItMightContain) const noexcept; + + /** Returns true if this string contains any non-whitespace characters. + + This will return false if the string contains only whitespace characters, or + if it's empty. + + It is equivalent to calling "myString.trim().isNotEmpty()". + */ + bool containsNonWhitespaceChars() const noexcept; + + /** Returns true if the string matches this simple wildcard expression. + + So for example String ("abcdef").matchesWildcard ("*DEF", true) would return true. + + This isn't a full-blown regex though! The only wildcard characters supported + are "*" and "?". It's mainly intended for filename pattern matching. + */ + bool matchesWildcard (const String& wildcard, bool ignoreCase) const noexcept; + + //============================================================================== + // Substring location methods.. + + /** Searches for a character inside this string. + Uses a case-sensitive comparison. + @returns the index of the first occurrence of the character in this + string, or -1 if it's not found. + */ + int indexOfChar (beast_wchar characterToLookFor) const noexcept; + + /** Searches for a character inside this string. + Uses a case-sensitive comparison. + @param startIndex the index from which the search should proceed + @param characterToLookFor the character to look for + @returns the index of the first occurrence of the character in this + string, or -1 if it's not found. + */ + int indexOfChar (int startIndex, beast_wchar characterToLookFor) const noexcept; + + /** Returns the index of the first character that matches one of the characters + passed-in to this method. + + This scans the string, beginning from the startIndex supplied, and if it finds + a character that appears in the string charactersToLookFor, it returns its index. + + If none of these characters are found, it returns -1. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see indexOfChar, lastIndexOfAnyOf + */ + int indexOfAnyOf (const String& charactersToLookFor, + int startIndex = 0, + bool ignoreCase = false) const noexcept; + + /** Searches for a substring within this string. + Uses a case-sensitive comparison. + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return 0. + */ + int indexOf (const String& textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-sensitive comparison. + @param startIndex the index from which the search should proceed + @param textToLookFor the string to search for + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return -1. + */ + int indexOf (int startIndex, const String& textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-insensitive comparison. + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return 0. + */ + int indexOfIgnoreCase (const String& textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-insensitive comparison. + @param startIndex the index from which the search should proceed + @param textToLookFor the string to search for + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return -1. + */ + int indexOfIgnoreCase (int startIndex, const String& textToLookFor) const noexcept; + + /** Searches for a character inside this string (working backwards from the end of the string). + Uses a case-sensitive comparison. + @returns the index of the last occurrence of the character in this string, or -1 if it's not found. + */ + int lastIndexOfChar (beast_wchar character) const noexcept; + + /** Searches for a substring inside this string (working backwards from the end of the string). + Uses a case-sensitive comparison. + @returns the index of the start of the last occurrence of the substring within this string, + or -1 if it's not found. If textToLookFor is an empty string, this will always return -1. + */ + int lastIndexOf (const String& textToLookFor) const noexcept; + + /** Searches for a substring inside this string (working backwards from the end of the string). + Uses a case-insensitive comparison. + @returns the index of the start of the last occurrence of the substring within this string, or -1 + if it's not found. If textToLookFor is an empty string, this will always return -1. + */ + int lastIndexOfIgnoreCase (const String& textToLookFor) const noexcept; + + /** Returns the index of the last character in this string that matches one of the + characters passed-in to this method. + + This scans the string backwards, starting from its end, and if it finds + a character that appears in the string charactersToLookFor, it returns its index. + + If none of these characters are found, it returns -1. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see lastIndexOf, indexOfAnyOf + */ + int lastIndexOfAnyOf (const String& charactersToLookFor, + bool ignoreCase = false) const noexcept; + + + //============================================================================== + // Substring extraction and manipulation methods.. + + /** Returns the character at this index in the string. + In a release build, no checks are made to see if the index is within a valid range, so be + careful! In a debug build, the index is checked and an assertion fires if it's out-of-range. + + Also beware that depending on the encoding format that the string is using internally, this + method may execute in either O(1) or O(n) time, so be careful when using it in your algorithms. + If you're scanning through a string to inspect its characters, you should never use this operator + for random access, it's far more efficient to call getCharPointer() to return a pointer, and + then to use that to iterate the string. + @see getCharPointer + */ + beast_wchar operator[] (int index) const noexcept; + + /** Returns the final character of the string. + If the string is empty this will return 0. + */ + beast_wchar getLastCharacter() const noexcept; + + //============================================================================== + /** Returns a subsection of the string. + + If the range specified is beyond the limits of the string, as much as + possible is returned. + + @param startIndex the index of the start of the substring needed + @param endIndex all characters from startIndex up to (but not including) + this index are returned + @see fromFirstOccurrenceOf, dropLastCharacters, getLastCharacters, upToFirstOccurrenceOf + */ + String substring (int startIndex, int endIndex) const; + + /** Returns a section of the string, starting from a given position. + + @param startIndex the first character to include. If this is beyond the end + of the string, an empty string is returned. If it is zero or + less, the whole string is returned. + @returns the substring from startIndex up to the end of the string + @see dropLastCharacters, getLastCharacters, fromFirstOccurrenceOf, upToFirstOccurrenceOf, fromLastOccurrenceOf + */ + String substring (int startIndex) const; + + /** Returns a version of this string with a number of characters removed + from the end. + + @param numberToDrop the number of characters to drop from the end of the + string. If this is greater than the length of the string, + an empty string will be returned. If zero or less, the + original string will be returned. + @see substring, fromFirstOccurrenceOf, upToFirstOccurrenceOf, fromLastOccurrenceOf, getLastCharacter + */ + String dropLastCharacters (int numberToDrop) const; + + /** Returns a number of characters from the end of the string. + + This returns the last numCharacters characters from the end of the string. If the + string is shorter than numCharacters, the whole string is returned. + + @see substring, dropLastCharacters, getLastCharacter + */ + String getLastCharacters (int numCharacters) const; + + //============================================================================== + /** Returns a section of the string starting from a given substring. + + This will search for the first occurrence of the given substring, and + return the section of the string starting from the point where this is + found (optionally not including the substring itself). + + e.g. for the string "123456", fromFirstOccurrenceOf ("34", true) would return "3456", and + fromFirstOccurrenceOf ("34", false) would return "56". + + If the substring isn't found, the method will return an empty string. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see upToFirstOccurrenceOf, fromLastOccurrenceOf + */ + String fromFirstOccurrenceOf (const String& substringToStartFrom, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns a section of the string starting from the last occurrence of a given substring. + + Similar to fromFirstOccurrenceOf(), but using the last occurrence of the substring, and + unlike fromFirstOccurrenceOf(), if the substring isn't found, this method will + return the whole of the original string. + + @see fromFirstOccurrenceOf, upToLastOccurrenceOf + */ + String fromLastOccurrenceOf (const String& substringToFind, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns the start of this string, up to the first occurrence of a substring. + + This will search for the first occurrence of a given substring, and then + return a copy of the string, up to the position of this substring, + optionally including or excluding the substring itself in the result. + + e.g. for the string "123456", upTo ("34", false) would return "12", and + upTo ("34", true) would return "1234". + + If the substring isn't found, this will return the whole of the original string. + + @see upToLastOccurrenceOf, fromFirstOccurrenceOf + */ + String upToFirstOccurrenceOf (const String& substringToEndWith, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns the start of this string, up to the last occurrence of a substring. + + Similar to upToFirstOccurrenceOf(), but this finds the last occurrence rather than the first. + If the substring isn't found, this will return the whole of the original string. + + @see upToFirstOccurrenceOf, fromFirstOccurrenceOf + */ + String upToLastOccurrenceOf (const String& substringToFind, + bool includeSubStringInResult, + bool ignoreCase) const; + + //============================================================================== + /** Returns a copy of this string with any whitespace characters removed from the start and end. */ + String trim() const; + + /** Returns a copy of this string with any whitespace characters removed from the start. */ + String trimStart() const; + + /** Returns a copy of this string with any whitespace characters removed from the end. */ + String trimEnd() const; + + /** Returns a copy of this string, having removed a specified set of characters from its start. + Characters are removed from the start of the string until it finds one that is not in the + specified set, and then it stops. + @param charactersToTrim the set of characters to remove. + @see trim, trimStart, trimCharactersAtEnd + */ + String trimCharactersAtStart (const String& charactersToTrim) const; + + /** Returns a copy of this string, having removed a specified set of characters from its end. + Characters are removed from the end of the string until it finds one that is not in the + specified set, and then it stops. + @param charactersToTrim the set of characters to remove. + @see trim, trimEnd, trimCharactersAtStart + */ + String trimCharactersAtEnd (const String& charactersToTrim) const; + + //============================================================================== + /** Returns an upper-case version of this string. */ + String toUpperCase() const; + + /** Returns an lower-case version of this string. */ + String toLowerCase() const; + + //============================================================================== + /** Replaces a sub-section of the string with another string. + + This will return a copy of this string, with a set of characters + from startIndex to startIndex + numCharsToReplace removed, and with + a new string inserted in their place. + + Note that this is a const method, and won't alter the string itself. + + @param startIndex the first character to remove. If this is beyond the bounds of the string, + it will be constrained to a valid range. + @param numCharactersToReplace the number of characters to remove. If zero or less, no + characters will be taken out. + @param stringToInsert the new string to insert at startIndex after the characters have been + removed. + */ + String replaceSection (int startIndex, + int numCharactersToReplace, + const String& stringToInsert) const; + + /** Replaces all occurrences of a substring with another string. + + Returns a copy of this string, with any occurrences of stringToReplace + swapped for stringToInsertInstead. + + Note that this is a const method, and won't alter the string itself. + */ + String replace (const String& stringToReplace, + const String& stringToInsertInstead, + bool ignoreCase = false) const; + + /** Returns a string with all occurrences of a character replaced with a different one. */ + String replaceCharacter (beast_wchar characterToReplace, + beast_wchar characterToInsertInstead) const; + + /** Replaces a set of characters with another set. + + Returns a string in which each character from charactersToReplace has been replaced + by the character at the equivalent position in newCharacters (so the two strings + passed in must be the same length). + + e.g. replaceCharacters ("abc", "def") replaces 'a' with 'd', 'b' with 'e', etc. + + Note that this is a const method, and won't affect the string itself. + */ + String replaceCharacters (const String& charactersToReplace, + const String& charactersToInsertInstead) const; + + /** Returns a version of this string that only retains a fixed set of characters. + + This will return a copy of this string, omitting any characters which are not + found in the string passed-in. + + e.g. for "1122334455", retainCharacters ("432") would return "223344" + + Note that this is a const method, and won't alter the string itself. + */ + String retainCharacters (const String& charactersToRetain) const; + + /** Returns a version of this string with a set of characters removed. + + This will return a copy of this string, omitting any characters which are + found in the string passed-in. + + e.g. for "1122334455", removeCharacters ("432") would return "1155" + + Note that this is a const method, and won't alter the string itself. + */ + String removeCharacters (const String& charactersToRemove) const; + + /** Returns a section from the start of the string that only contains a certain set of characters. + + This returns the leftmost section of the string, up to (and not including) the + first character that doesn't appear in the string passed in. + */ + String initialSectionContainingOnly (const String& permittedCharacters) const; + + /** Returns a section from the start of the string that only contains a certain set of characters. + + This returns the leftmost section of the string, up to (and not including) the + first character that occurs in the string passed in. (If none of the specified + characters are found in the string, the return value will just be the original string). + */ + String initialSectionNotContaining (const String& charactersToStopAt) const; + + //============================================================================== + /** Checks whether the string might be in quotation marks. + + @returns true if the string begins with a quote character (either a double or single quote). + It is also true if there is whitespace before the quote, but it doesn't check the end of the string. + @see unquoted, quoted + */ + bool isQuotedString() const; + + /** Removes quotation marks from around the string, (if there are any). + + Returns a copy of this string with any quotes removed from its ends. Quotes that aren't + at the ends of the string are not affected. If there aren't any quotes, the original string + is returned. + + Note that this is a const method, and won't alter the string itself. + + @see isQuotedString, quoted + */ + String unquoted() const; + + /** Adds quotation marks around a string. + + This will return a copy of the string with a quote at the start and end, (but won't + add the quote if there's already one there, so it's safe to call this on strings that + may already have quotes around them). + + Note that this is a const method, and won't alter the string itself. + + @param quoteCharacter the character to add at the start and end + @see isQuotedString, unquoted + */ + String quoted (beast_wchar quoteCharacter = '"') const; + + + //============================================================================== + /** Creates a string which is a version of a string repeated and joined together. + + @param stringToRepeat the string to repeat + @param numberOfTimesToRepeat how many times to repeat it + */ + static String repeatedString (const String& stringToRepeat, + int numberOfTimesToRepeat); + + /** Returns a copy of this string with the specified character repeatedly added to its + beginning until the total length is at least the minimum length specified. + */ + String paddedLeft (beast_wchar padCharacter, int minimumLength) const; + + /** Returns a copy of this string with the specified character repeatedly added to its + end until the total length is at least the minimum length specified. + */ + String paddedRight (beast_wchar padCharacter, int minimumLength) const; + + /** Creates a string from data in an unknown format. + + This looks at some binary data and tries to guess whether it's Unicode + or 8-bit characters, then returns a string that represents it correctly. + + Should be able to handle Unicode endianness correctly, by looking at + the first two bytes. + */ + static String createStringFromData (const void* data, int size); + + /** Creates a String from a printf-style parameter list. + + I don't like this method. I don't use it myself, and I recommend avoiding it and + using the operator<< methods or pretty much anything else instead. It's only provided + here because of the popular unrest that was stirred-up when I tried to remove it... + + If you're really determined to use it, at least make sure that you never, ever, + pass any String objects to it as parameters. And bear in mind that internally, depending + on the platform, it may be using wchar_t or char character types, so that even string + literals can't be safely used as parameters if you're writing portable code. + */ + static String formatted (const String formatString, ... ); + + //============================================================================== + // Numeric conversions.. + + /** Creates a string containing this signed 32-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (int decimalInteger); + + /** Creates a string containing this unsigned 32-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned int decimalInteger); + + /** Creates a string containing this signed 16-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (short decimalInteger); + + /** Creates a string containing this unsigned 16-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned short decimalInteger); + + /** Creates a string containing this signed 64-bit integer as a decimal number. + @see getLargeIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (int64 largeIntegerValue); + + /** Creates a string containing this unsigned 64-bit integer as a decimal number. + @see getLargeIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (uint64 largeIntegerValue); + + /** Creates a string representing this floating-point number. + @param floatValue the value to convert to a string + @see getDoubleValue, getIntValue + */ + explicit String (float floatValue); + + /** Creates a string representing this floating-point number. + @param doubleValue the value to convert to a string + @see getFloatValue, getIntValue + */ + explicit String (double doubleValue); + + /** Creates a string representing this floating-point number. + @param floatValue the value to convert to a string + @param numberOfDecimalPlaces if this is > 0, it will format the number using that many + decimal places, and will not use exponent notation. If 0 or + less, it will use exponent notation if necessary. + @see getDoubleValue, getIntValue + */ + String (float floatValue, int numberOfDecimalPlaces); + + /** Creates a string representing this floating-point number. + @param doubleValue the value to convert to a string + @param numberOfDecimalPlaces if this is > 0, it will format the number using that many + decimal places, and will not use exponent notation. If 0 or + less, it will use exponent notation if necessary. + @see getFloatValue, getIntValue + */ + String (double doubleValue, int numberOfDecimalPlaces); + + /** Reads the value of the string as a decimal number (up to 32 bits in size). + + @returns the value of the string as a 32 bit signed base-10 integer. + @see getTrailingIntValue, getHexValue32, getHexValue64 + */ + int getIntValue() const noexcept; + + /** Reads the value of the string as a decimal number (up to 64 bits in size). + + @returns the value of the string as a 64 bit signed base-10 integer. + */ + int64 getLargeIntValue() const noexcept; + + /** Parses a decimal number from the end of the string. + + This will look for a value at the end of the string. + e.g. for "321 xyz654" it will return 654; for "2 3 4" it'll return 4. + + Negative numbers are not handled, so "xyz-5" returns 5. + + @see getIntValue + */ + int getTrailingIntValue() const noexcept; + + /** Parses this string as a floating point number. + + @returns the value of the string as a 32-bit floating point value. + @see getDoubleValue + */ + float getFloatValue() const noexcept; + + /** Parses this string as a floating point number. + + @returns the value of the string as a 64-bit floating point value. + @see getFloatValue + */ + double getDoubleValue() const noexcept; + + /** Parses the string as a hexadecimal number. + + Non-hexadecimal characters in the string are ignored. + + If the string contains too many characters, then the lowest significant + digits are returned, e.g. "ffff12345678" would produce 0x12345678. + + @returns a 32-bit number which is the value of the string in hex. + */ + int getHexValue32() const noexcept; + + /** Parses the string as a hexadecimal number. + + Non-hexadecimal characters in the string are ignored. + + If the string contains too many characters, then the lowest significant + digits are returned, e.g. "ffff1234567812345678" would produce 0x1234567812345678. + + @returns a 64-bit number which is the value of the string in hex. + */ + int64 getHexValue64() const noexcept; + + /** Creates a string representing this 32-bit value in hexadecimal. */ + static String toHexString (int number); + + /** Creates a string representing this 64-bit value in hexadecimal. */ + static String toHexString (int64 number); + + /** Creates a string representing this 16-bit value in hexadecimal. */ + static String toHexString (short number); + + /** Creates a string containing a hex dump of a block of binary data. + + @param data the binary data to use as input + @param size how many bytes of data to use + @param groupSize how many bytes are grouped together before inserting a + space into the output. e.g. group size 0 has no spaces, + group size 1 looks like: "be a1 c2 ff", group size 2 looks + like "bea1 c2ff". + */ + static String toHexString (const void* data, int size, int groupSize = 1); + + //============================================================================== + /** Returns the character pointer currently being used to store this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + */ + inline CharPointerType getCharPointer() const noexcept { return text; } + + /** Returns a pointer to a UTF-8 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @see toRawUTF8, getCharPointer, toUTF16, toUTF32 + */ + CharPointer_UTF8 toUTF8() const; + + /** Returns a pointer to a UTF-8 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @see getCharPointer, toUTF8, toUTF16, toUTF32 + */ + const char* toRawUTF8() const; + + /** Returns a pointer to a UTF-16 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-16, you can call + CharPointer_UTF16::getBytesRequiredFor (myString.getCharPointer()) + + @see getCharPointer, toUTF8, toUTF32 + */ + CharPointer_UTF16 toUTF16() const; + + /** Returns a pointer to a UTF-32 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + @see getCharPointer, toUTF8, toUTF16 + */ + CharPointer_UTF32 toUTF32() const; + + /** Returns a pointer to a wchar_t version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + Bear in mind that the wchar_t type is different on different platforms, so on + Windows, this will be equivalent to calling toUTF16(), on unix it'll be the same + as calling toUTF32(), etc. + + @see getCharPointer, toUTF8, toUTF16, toUTF32 + */ + const wchar_t* toWideCharPointer() const; + + /** */ + std::string toStdString() const; + + //============================================================================== + /** Creates a String from a UTF-8 encoded buffer. + If the size is < 0, it'll keep reading until it hits a zero. + */ + static String fromUTF8 (const char* utf8buffer, int bufferSizeBytes = -1); + + /** Returns the number of bytes required to represent this string as UTF8. + The number returned does NOT include the trailing zero. + @see toUTF8, copyToUTF8 + */ + size_t getNumBytesAsUTF8() const noexcept; + + //============================================================================== + /** Copies the string to a buffer as UTF-8 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF8::writeWithDestByteLimit + */ + size_t copyToUTF8 (CharPointer_UTF8::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + /** Copies the string to a buffer as UTF-16 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-16, you can call + CharPointer_UTF16::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF16::writeWithDestByteLimit + */ + size_t copyToUTF16 (CharPointer_UTF16::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + /** Copies the string to a buffer as UTF-32 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-32, you can call + CharPointer_UTF32::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF32::writeWithDestByteLimit + */ + size_t copyToUTF32 (CharPointer_UTF32::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + //============================================================================== + /** Increases the string's internally allocated storage. + + Although the string's contents won't be affected by this call, it will + increase the amount of memory allocated internally for the string to grow into. + + If you're about to make a large number of calls to methods such + as += or <<, it's more efficient to preallocate enough extra space + beforehand, so that these methods won't have to keep resizing the string + to append the extra characters. + + @param numBytesNeeded the number of bytes to allocate storage for. If this + value is less than the currently allocated size, it will + have no effect. + */ + void preallocateBytes (size_t numBytesNeeded); + + /** Swaps the contents of this string with another one. + This is a very fast operation, as no allocation or copying needs to be done. + */ + void swapWith (String& other) noexcept; + + //============================================================================== + #if BEAST_MAC || BEAST_IOS || DOXYGEN + /** MAC ONLY - Creates a String from an OSX CFString. */ + static String fromCFString (CFStringRef cfString); + + /** MAC ONLY - Converts this string to a CFString. + Remember that you must use CFRelease() to free the returned string when you're + finished with it. + */ + CFStringRef toCFString() const; + + /** MAC ONLY - Returns a copy of this string in which any decomposed unicode characters have + been converted to their precomposed equivalents. */ + String convertToPrecomposedUnicode() const; + #endif + +private: + //============================================================================== + CharPointerType text; + + //============================================================================== + struct PreallocationBytes + { + explicit PreallocationBytes (size_t); + size_t numBytes; + }; + + explicit String (const PreallocationBytes&); // This constructor preallocates a certain amount of memory + void appendFixedLength (const char* text, int numExtraChars); + size_t getByteOffsetOfEnd() const noexcept; + BEAST_DEPRECATED (String (const String& stringToCopy, size_t charsToAllocate)); + + // This private cast operator should prevent strings being accidentally cast + // to bools (this is possible because the compiler can add an implicit cast + // via a const char*) + operator bool() const noexcept { return false; } +}; + +//============================================================================== +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (const char* string1, const String& string2); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (const wchar_t* string1, const String& string2); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (char string1, const String& string2); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (wchar_t string1, const String& string2); +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (beast_wchar string1, const String& string2); +#endif + +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (String string1, const String& string2); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (String string1, const char* string2); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (String string1, const wchar_t* string2); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (String string1, char characterToAppend); +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (String string1, wchar_t characterToAppend); +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +/** Concatenates two strings. */ +BEAST_API String BEAST_CALLTYPE operator+ (String string1, beast_wchar characterToAppend); +#endif + +//============================================================================== +/** Appends a character at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, char characterToAppend); +/** Appends a character at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, wchar_t characterToAppend); +#if ! BEAST_NATIVE_WCHAR_IS_UTF32 +/** Appends a character at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, beast_wchar characterToAppend); +#endif + +/** Appends a string to the end of the first one. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, const char* string2); +/** Appends a string to the end of the first one. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, const wchar_t* string2); +/** Appends a string to the end of the first one. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, const String& string2); + +/** Appends a decimal number at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, short number); +/** Appends a decimal number at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, int number); +/** Appends a decimal number at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, long number); +/** Appends a decimal number at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, int64 number); +/** Appends a decimal number at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, float number); +/** Appends a decimal number at the end of a string. */ +BEAST_API String& BEAST_CALLTYPE operator<< (String& string1, double number); + +//============================================================================== +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator== (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator== (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator== (const String& string1, const wchar_t* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator== (const String& string1, const CharPointer_UTF8 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator== (const String& string1, const CharPointer_UTF16 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator== (const String& string1, const CharPointer_UTF32 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator!= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator!= (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator!= (const String& string1, const wchar_t* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator!= (const String& string1, const CharPointer_UTF8 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator!= (const String& string1, const CharPointer_UTF16 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator!= (const String& string1, const CharPointer_UTF32 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator> (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator< (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator>= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +BEAST_API bool BEAST_CALLTYPE operator<= (const String& string1, const String& string2) noexcept; + +//============================================================================== +/** This operator allows you to write a beast String directly to std output streams. + This is handy for writing strings to std::cout, std::cerr, etc. +*/ +template +std::basic_ostream & BEAST_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toRawUTF8(); +} + +/** This operator allows you to write a beast String directly to std output streams. + This is handy for writing strings to std::wcout, std::wcerr, etc. +*/ +template +std::basic_ostream & BEAST_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toWideCharPointer(); +} + +/** Writes a string to an OutputStream as UTF8. */ +BEAST_API OutputStream& BEAST_CALLTYPE operator<< (OutputStream& stream, const String& stringToWrite); + + +#endif // BEAST_STRING_BEASTHEADER diff --git a/modules/beast_core/text/beast_StringArray.cpp b/modules/beast_core/text/beast_StringArray.cpp new file mode 100644 index 0000000000..d4770d2bd7 --- /dev/null +++ b/modules/beast_core/text/beast_StringArray.cpp @@ -0,0 +1,505 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +StringArray::StringArray() noexcept +{ +} + +StringArray::StringArray (const StringArray& other) + : strings (other.strings) +{ +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +StringArray::StringArray (StringArray&& other) noexcept + : strings (static_cast &&> (other.strings)) +{ +} +#endif + +StringArray::StringArray (const String& firstValue) +{ + strings.add (firstValue); +} + +namespace StringArrayHelpers +{ + template + void addArray (Array& dest, const CharType* const* strings) + { + if (strings != nullptr) + while (*strings != nullptr) + dest.add (*strings++); + } + + template + void addArray (Array& dest, const Type* const strings, const int numberOfStrings) + { + for (int i = 0; i < numberOfStrings; ++i) + dest.add (strings [i]); + } +} + +StringArray::StringArray (const String* initialStrings, int numberOfStrings) +{ + StringArrayHelpers::addArray (strings, initialStrings, numberOfStrings); +} + +StringArray::StringArray (const char* const* const initialStrings) +{ + StringArrayHelpers::addArray (strings, initialStrings); +} + +StringArray::StringArray (const char* const* const initialStrings, const int numberOfStrings) +{ + StringArrayHelpers::addArray (strings, initialStrings, numberOfStrings); +} + +StringArray::StringArray (const wchar_t* const* const initialStrings) +{ + StringArrayHelpers::addArray (strings, initialStrings); +} + +StringArray::StringArray (const wchar_t* const* const initialStrings, const int numberOfStrings) +{ + StringArrayHelpers::addArray (strings, initialStrings, numberOfStrings); +} + +StringArray& StringArray::operator= (const StringArray& other) +{ + strings = other.strings; + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +StringArray& StringArray::operator= (StringArray&& other) noexcept +{ + strings = static_cast &&> (other.strings); + return *this; +} +#endif + +StringArray::~StringArray() +{ +} + +bool StringArray::operator== (const StringArray& other) const noexcept +{ + if (other.size() != size()) + return false; + + for (int i = size(); --i >= 0;) + if (other.strings.getReference(i) != strings.getReference(i)) + return false; + + return true; +} + +bool StringArray::operator!= (const StringArray& other) const noexcept +{ + return ! operator== (other); +} + +void StringArray::swapWith (StringArray& other) noexcept +{ + strings.swapWithArray (other.strings); +} + +void StringArray::clear() +{ + strings.clear(); +} + +const String& StringArray::operator[] (const int index) const noexcept +{ + if (isPositiveAndBelow (index, strings.size())) + return strings.getReference (index); + + return String::empty; +} + +String& StringArray::getReference (const int index) noexcept +{ + bassert (isPositiveAndBelow (index, strings.size())); + return strings.getReference (index); +} + +void StringArray::add (const String& newString) +{ + strings.add (newString); +} + +void StringArray::insert (const int index, const String& newString) +{ + strings.insert (index, newString); +} + +void StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) +{ + if (! contains (newString, ignoreCase)) + add (newString); +} + +void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) +{ + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size()) + numElementsToAdd = otherArray.size() - startIndex; + + while (--numElementsToAdd >= 0) + strings.add (otherArray.strings.getReference (startIndex++)); +} + +void StringArray::set (const int index, const String& newString) +{ + strings.set (index, newString); +} + +bool StringArray::contains (const String& stringToLookFor, const bool ignoreCase) const +{ + if (ignoreCase) + { + for (int i = size(); --i >= 0;) + if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) + return true; + } + else + { + for (int i = size(); --i >= 0;) + if (stringToLookFor == strings.getReference(i)) + return true; + } + + return false; +} + +int StringArray::indexOf (const String& stringToLookFor, const bool ignoreCase, int i) const +{ + if (i < 0) + i = 0; + + const int numElements = size(); + + if (ignoreCase) + { + while (i < numElements) + { + if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) + return i; + + ++i; + } + } + else + { + while (i < numElements) + { + if (stringToLookFor == strings.getReference (i)) + return i; + + ++i; + } + } + + return -1; +} + +//============================================================================== +void StringArray::remove (const int index) +{ + strings.remove (index); +} + +void StringArray::removeString (const String& stringToRemove, + const bool ignoreCase) +{ + if (ignoreCase) + { + for (int i = size(); --i >= 0;) + if (strings.getReference(i).equalsIgnoreCase (stringToRemove)) + strings.remove (i); + } + else + { + for (int i = size(); --i >= 0;) + if (stringToRemove == strings.getReference (i)) + strings.remove (i); + } +} + +void StringArray::removeRange (int startIndex, int numberToRemove) +{ + strings.removeRange (startIndex, numberToRemove); +} + +//============================================================================== +void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) +{ + if (removeWhitespaceStrings) + { + for (int i = size(); --i >= 0;) + if (! strings.getReference(i).containsNonWhitespaceChars()) + strings.remove (i); + } + else + { + for (int i = size(); --i >= 0;) + if (strings.getReference(i).isEmpty()) + strings.remove (i); + } +} + +void StringArray::trim() +{ + for (int i = size(); --i >= 0;) + { + String& s = strings.getReference(i); + s = s.trim(); + } +} + +//============================================================================== +struct InternalStringArrayComparator_CaseSensitive +{ + static int compareElements (String& first, String& second) { return first.compare (second); } +}; + +struct InternalStringArrayComparator_CaseInsensitive +{ + static int compareElements (String& first, String& second) { return first.compareIgnoreCase (second); } +}; + +void StringArray::sort (const bool ignoreCase) +{ + if (ignoreCase) + { + InternalStringArrayComparator_CaseInsensitive comp; + strings.sort (comp); + } + else + { + InternalStringArrayComparator_CaseSensitive comp; + strings.sort (comp); + } +} + +void StringArray::move (const int currentIndex, int newIndex) noexcept +{ + strings.move (currentIndex, newIndex); +} + + +//============================================================================== +String StringArray::joinIntoString (const String& separator, int start, int numberToJoin) const +{ + const int last = (numberToJoin < 0) ? size() + : bmin (size(), start + numberToJoin); + + if (start < 0) + start = 0; + + if (start >= last) + return String::empty; + + if (start == last - 1) + return strings.getReference (start); + + const size_t separatorBytes = separator.getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); + size_t bytesNeeded = separatorBytes * (size_t) (last - start - 1); + + for (int i = start; i < last; ++i) + bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); + + String result; + result.preallocateBytes (bytesNeeded); + + String::CharPointerType dest (result.getCharPointer()); + + while (start < last) + { + const String& s = strings.getReference (start); + + if (! s.isEmpty()) + dest.writeAll (s.getCharPointer()); + + if (++start < last && separatorBytes > 0) + dest.writeAll (separator.getCharPointer()); + } + + dest.writeNull(); + + return result; +} + +int StringArray::addTokens (const String& text, const bool preserveQuotedStrings) +{ + return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); +} + +int StringArray::addTokens (const String& text, const String& breakCharacters, const String& quoteCharacters) +{ + int num = 0; + String::CharPointerType t (text.getCharPointer()); + + if (! t.isEmpty()) + { + for (;;) + { + String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t, + breakCharacters.getCharPointer(), + quoteCharacters.getCharPointer())); + strings.add (String (t, tokenEnd)); + ++num; + + if (tokenEnd.isEmpty()) + break; + + t = ++tokenEnd; + } + } + + return num; +} + +int StringArray::addLines (const String& sourceText) +{ + int numLines = 0; + String::CharPointerType text (sourceText.getCharPointer()); + bool finished = text.isEmpty(); + + while (! finished) + { + for (String::CharPointerType startOfLine (text);;) + { + const String::CharPointerType endOfLine (text); + + switch (text.getAndAdvance()) + { + case 0: finished = true; break; + case '\n': break; + case '\r': if (*text == '\n') ++text; break; + default: continue; + } + + strings.add (String (startOfLine, endOfLine)); + ++numLines; + break; + } + } + + return numLines; +} + +StringArray StringArray::fromTokens (const String& stringToTokenise, + bool preserveQuotedStrings) +{ + StringArray s; + s.addTokens (stringToTokenise, preserveQuotedStrings); + return s; +} + +StringArray StringArray::fromTokens (const String& stringToTokenise, + const String& breakCharacters, + const String& quoteCharacters) +{ + StringArray s; + s.addTokens (stringToTokenise, breakCharacters, quoteCharacters); + return s; +} + +StringArray StringArray::fromLines (const String& stringToBreakUp) +{ + StringArray s; + s.addLines (stringToBreakUp); + return s; +} + +//============================================================================== +void StringArray::removeDuplicates (const bool ignoreCase) +{ + for (int i = 0; i < size() - 1; ++i) + { + const String s (strings.getReference(i)); + + int nextIndex = i + 1; + + for (;;) + { + nextIndex = indexOf (s, ignoreCase, nextIndex); + + if (nextIndex < 0) + break; + + strings.remove (nextIndex); + } + } +} + +void StringArray::appendNumbersToDuplicates (const bool ignoreCase, + const bool appendNumberToFirstInstance, + CharPointer_UTF8 preNumberString, + CharPointer_UTF8 postNumberString) +{ + CharPointer_UTF8 defaultPre (" ("), defaultPost (")"); + + if (preNumberString.getAddress() == nullptr) + preNumberString = defaultPre; + + if (postNumberString.getAddress() == nullptr) + postNumberString = defaultPost; + + for (int i = 0; i < size() - 1; ++i) + { + String& s = strings.getReference(i); + + int nextIndex = indexOf (s, ignoreCase, i + 1); + + if (nextIndex >= 0) + { + const String original (s); + + int number = 0; + + if (appendNumberToFirstInstance) + s = original + String (preNumberString) + String (++number) + String (postNumberString); + else + ++number; + + while (nextIndex >= 0) + { + set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString)); + nextIndex = indexOf (original, ignoreCase, nextIndex + 1); + } + } + } +} + +void StringArray::minimiseStorageOverheads() +{ + strings.minimiseStorageOverheads(); +} diff --git a/modules/beast_core/text/beast_StringArray.h b/modules/beast_core/text/beast_StringArray.h new file mode 100644 index 0000000000..26d1536cea --- /dev/null +++ b/modules/beast_core/text/beast_StringArray.h @@ -0,0 +1,406 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_STRINGARRAY_BEASTHEADER +#define BEAST_STRINGARRAY_BEASTHEADER + +#include "beast_String.h" +#include "../containers/beast_Array.h" + + +//============================================================================== +/** + A special array for holding a list of strings. + + @see String, StringPairArray +*/ +class BEAST_API StringArray +{ +public: + //============================================================================== + /** Creates an empty string array */ + StringArray() noexcept; + + /** Creates a copy of another string array */ + StringArray (const StringArray& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + StringArray (StringArray&& other) noexcept; + #endif + + /** Creates an array containing a single string. */ + explicit StringArray (const String& firstValue); + + /** Creates an array from a raw array of strings. + @param strings an array of strings to add + @param numberOfStrings how many items there are in the array + */ + StringArray (const String* strings, int numberOfStrings); + + /** Creates a copy of an array of string literals. + @param strings an array of strings to add. Null pointers in the array will be + treated as empty strings + @param numberOfStrings how many items there are in the array + */ + StringArray (const char* const* strings, int numberOfStrings); + + /** Creates a copy of a null-terminated array of string literals. + + Each item from the array passed-in is added, until it encounters a null pointer, + at which point it stops. + */ + explicit StringArray (const char* const* strings); + + /** Creates a copy of a null-terminated array of string literals. + Each item from the array passed-in is added, until it encounters a null pointer, + at which point it stops. + */ + explicit StringArray (const wchar_t* const* strings); + + /** Creates a copy of an array of string literals. + @param strings an array of strings to add. Null pointers in the array will be + treated as empty strings + @param numberOfStrings how many items there are in the array + */ + StringArray (const wchar_t* const* strings, int numberOfStrings); + + /** Destructor. */ + ~StringArray(); + + /** Copies the contents of another string array into this one */ + StringArray& operator= (const StringArray& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + StringArray& operator= (StringArray&& other) noexcept; + #endif + + /** Swaps the contents of this and another StringArray. */ + void swapWith (StringArray& other) noexcept; + + //============================================================================== + /** Compares two arrays. + Comparisons are case-sensitive. + @returns true only if the other array contains exactly the same strings in the same order + */ + bool operator== (const StringArray& other) const noexcept; + + /** Compares two arrays. + Comparisons are case-sensitive. + @returns false if the other array contains exactly the same strings in the same order + */ + bool operator!= (const StringArray& other) const noexcept; + + //============================================================================== + /** Returns the number of strings in the array */ + inline int size() const noexcept { return strings.size(); }; + + /** Returns one of the strings from the array. + + If the index is out-of-range, an empty string is returned. + + Obviously the reference returned shouldn't be stored for later use, as the + string it refers to may disappear when the array changes. + */ + const String& operator[] (int index) const noexcept; + + /** Returns a reference to one of the strings in the array. + This lets you modify a string in-place in the array, but you must be sure that + the index is in-range. + */ + String& getReference (int index) noexcept; + + /** Returns a pointer to the first String in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline String* begin() const noexcept + { + return strings.begin(); + } + + /** Returns a pointer to the String which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline String* end() const noexcept + { + return strings.end(); + } + + /** Searches for a string in the array. + + The comparison will be case-insensitive if the ignoreCase parameter is true. + + @returns true if the string is found inside the array + */ + bool contains (const String& stringToLookFor, + bool ignoreCase = false) const; + + /** Searches for a string in the array. + + The comparison will be case-insensitive if the ignoreCase parameter is true. + + @param stringToLookFor the string to try to find + @param ignoreCase whether the comparison should be case-insensitive + @param startIndex the first index to start searching from + @returns the index of the first occurrence of the string in this array, + or -1 if it isn't found. + */ + int indexOf (const String& stringToLookFor, + bool ignoreCase = false, + int startIndex = 0) const; + + //============================================================================== + /** Appends a string at the end of the array. */ + void add (const String& stringToAdd); + + /** Inserts a string into the array. + + This will insert a string into the array at the given index, moving + up the other elements to make room for it. + If the index is less than zero or greater than the size of the array, + the new string will be added to the end of the array. + */ + void insert (int index, const String& stringToAdd); + + /** Adds a string to the array as long as it's not already in there. + + The search can optionally be case-insensitive. + */ + void addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false); + + /** Replaces one of the strings in the array with another one. + + If the index is higher than the array's size, the new string will be + added to the end of the array; if it's less than zero nothing happens. + */ + void set (int index, const String& newString); + + /** Appends some strings from another array to the end of this one. + + @param other the array to add + @param startIndex the first element of the other array to add + @param numElementsToAdd the maximum number of elements to add (if this is + less than zero, they are all added) + */ + void addArray (const StringArray& other, + int startIndex = 0, + int numElementsToAdd = -1); + + /** Breaks up a string into tokens and adds them to this array. + + This will tokenise the given string using whitespace characters as the + token delimiters, and will add these tokens to the end of the array. + @returns the number of tokens added + @see fromTokens + */ + int addTokens (const String& stringToTokenise, + bool preserveQuotedStrings); + + /** Breaks up a string into tokens and adds them to this array. + + This will tokenise the given string (using the string passed in to define the + token delimiters), and will add these tokens to the end of the array. + + @param stringToTokenise the string to tokenise + @param breakCharacters a string of characters, any of which will be considered + to be a token delimiter. + @param quoteCharacters if this string isn't empty, it defines a set of characters + which are treated as quotes. Any text occurring + between quotes is not broken up into tokens. + @returns the number of tokens added + @see fromTokens + */ + int addTokens (const String& stringToTokenise, + const String& breakCharacters, + const String& quoteCharacters); + + /** Breaks up a string into lines and adds them to this array. + + This breaks a string down into lines separated by \\n or \\r\\n, and adds each line + to the array. Line-break characters are omitted from the strings that are added to + the array. + */ + int addLines (const String& stringToBreakUp); + + /** Returns an array containing the tokens in a given string. + + This will tokenise the given string using whitespace characters as the + token delimiters, and return these tokens as an array. + @see addTokens + */ + static StringArray fromTokens (const String& stringToTokenise, + bool preserveQuotedStrings); + + /** Returns an array containing the tokens in a given string. + + This will tokenise the given string using whitespace characters as the + token delimiters, and return these tokens as an array. + + @param stringToTokenise the string to tokenise + @param breakCharacters a string of characters, any of which will be considered + to be a token delimiter. + @param quoteCharacters if this string isn't empty, it defines a set of characters + which are treated as quotes. Any text occurring + between quotes is not broken up into tokens. + @see addTokens + */ + static StringArray fromTokens (const String& stringToTokenise, + const String& breakCharacters, + const String& quoteCharacters); + + /** Returns an array containing the lines in a given string. + + This breaks a string down into lines separated by \\n or \\r\\n, and returns an + array containing these lines. Line-break characters are omitted from the strings that + are added to the array. + */ + static StringArray fromLines (const String& stringToBreakUp); + + //============================================================================== + /** Removes all elements from the array. */ + void clear(); + + /** Removes a string from the array. + + If the index is out-of-range, no action will be taken. + */ + void remove (int index); + + /** Finds a string in the array and removes it. + + This will remove the first occurrence of the given string from the array. The + comparison may be case-insensitive depending on the ignoreCase parameter. + */ + void removeString (const String& stringToRemove, + bool ignoreCase = false); + + /** Removes a range of elements from the array. + + This will remove a set of elements, starting from the given index, + and move subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first element to remove + @param numberToRemove how many elements should be removed + */ + void removeRange (int startIndex, int numberToRemove); + + /** Removes any duplicated elements from the array. + + If any string appears in the array more than once, only the first occurrence of + it will be retained. + + @param ignoreCase whether to use a case-insensitive comparison + */ + void removeDuplicates (bool ignoreCase); + + /** Removes empty strings from the array. + + @param removeWhitespaceStrings if true, strings that only contain whitespace + characters will also be removed + */ + void removeEmptyStrings (bool removeWhitespaceStrings = true); + + /** Moves one of the strings to a different position. + + This will move the string to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the value to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this value to end up. If this + is less than zero, the value will be moved to the end + of the array + */ + void move (int currentIndex, int newIndex) noexcept; + + /** Deletes any whitespace characters from the starts and ends of all the strings. */ + void trim(); + + /** Adds numbers to the strings in the array, to make each string unique. + + This will add numbers to the ends of groups of similar strings. + e.g. if there are two "moose" strings, they will become "moose (1)" and "moose (2)" + + @param ignoreCaseWhenComparing whether the comparison used is case-insensitive + @param appendNumberToFirstInstance whether the first of a group of similar strings + also has a number appended to it. + @param preNumberString when adding a number, this string is added before the number. + If you pass 0, a default string will be used, which adds + brackets around the number. + @param postNumberString this string is appended after any numbers that are added. + If you pass 0, a default string will be used, which adds + brackets around the number. + */ + void appendNumbersToDuplicates (bool ignoreCaseWhenComparing, + bool appendNumberToFirstInstance, + CharPointer_UTF8 preNumberString = CharPointer_UTF8 (nullptr), + CharPointer_UTF8 postNumberString = CharPointer_UTF8 (nullptr)); + + //============================================================================== + /** Joins the strings in the array together into one string. + + This will join a range of elements from the array into a string, separating + them with a given string. + + e.g. joinIntoString (",") will turn an array of "a" "b" and "c" into "a,b,c". + + @param separatorString the string to insert between all the strings + @param startIndex the first element to join + @param numberOfElements how many elements to join together. If this is less + than zero, all available elements will be used. + */ + String joinIntoString (const String& separatorString, + int startIndex = 0, + int numberOfElements = -1) const; + + //============================================================================== + /** Sorts the array into alphabetical order. + + @param ignoreCase if true, the comparisons used will be case-sensitive. + */ + void sort (bool ignoreCase); + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads(); + + +private: + //============================================================================== + Array strings; + + BEAST_LEAK_DETECTOR (StringArray) +}; + + +#endif // BEAST_STRINGARRAY_BEASTHEADER diff --git a/modules/beast_core/text/beast_StringPairArray.cpp b/modules/beast_core/text/beast_StringPairArray.cpp new file mode 100644 index 0000000000..f325556f64 --- /dev/null +++ b/modules/beast_core/text/beast_StringPairArray.cpp @@ -0,0 +1,137 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +StringPairArray::StringPairArray (const bool ignoreCase_) + : ignoreCase (ignoreCase_) +{ +} + +StringPairArray::StringPairArray (const StringPairArray& other) + : keys (other.keys), + values (other.values), + ignoreCase (other.ignoreCase) +{ +} + +StringPairArray::~StringPairArray() +{ +} + +StringPairArray& StringPairArray::operator= (const StringPairArray& other) +{ + keys = other.keys; + values = other.values; + return *this; +} + +bool StringPairArray::operator== (const StringPairArray& other) const +{ + for (int i = keys.size(); --i >= 0;) + if (other [keys[i]] != values[i]) + return false; + + return true; +} + +bool StringPairArray::operator!= (const StringPairArray& other) const +{ + return ! operator== (other); +} + +const String& StringPairArray::operator[] (const String& key) const +{ + return values [keys.indexOf (key, ignoreCase)]; +} + +String StringPairArray::getValue (const String& key, const String& defaultReturnValue) const +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + return values[i]; + + return defaultReturnValue; +} + +void StringPairArray::set (const String& key, const String& value) +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + { + values.set (i, value); + } + else + { + keys.add (key); + values.add (value); + } +} + +void StringPairArray::addArray (const StringPairArray& other) +{ + for (int i = 0; i < other.size(); ++i) + set (other.keys[i], other.values[i]); +} + +void StringPairArray::clear() +{ + keys.clear(); + values.clear(); +} + +void StringPairArray::remove (const String& key) +{ + remove (keys.indexOf (key, ignoreCase)); +} + +void StringPairArray::remove (const int index) +{ + keys.remove (index); + values.remove (index); +} + +void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) +{ + ignoreCase = shouldIgnoreCase; +} + +String StringPairArray::getDescription() const +{ + String s; + + for (int i = 0; i < keys.size(); ++i) + { + s << keys[i] << " = " << values[i]; + if (i < keys.size()) + s << ", "; + } + + return s; +} + +void StringPairArray::minimiseStorageOverheads() +{ + keys.minimiseStorageOverheads(); + values.minimiseStorageOverheads(); +} diff --git a/modules/beast_core/text/beast_StringPairArray.h b/modules/beast_core/text/beast_StringPairArray.h new file mode 100644 index 0000000000..a3eac9b3bf --- /dev/null +++ b/modules/beast_core/text/beast_StringPairArray.h @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_STRINGPAIRARRAY_BEASTHEADER +#define BEAST_STRINGPAIRARRAY_BEASTHEADER + +#include "beast_StringArray.h" + + +//============================================================================== +/** + A container for holding a set of strings which are keyed by another string. + + @see StringArray +*/ +class BEAST_API StringPairArray +{ +public: + //============================================================================== + /** Creates an empty array */ + StringPairArray (bool ignoreCaseWhenComparingKeys = true); + + /** Creates a copy of another array */ + StringPairArray (const StringPairArray& other); + + /** Destructor. */ + ~StringPairArray(); + + /** Copies the contents of another string array into this one */ + StringPairArray& operator= (const StringPairArray& other); + + //============================================================================== + /** Compares two arrays. + Comparisons are case-sensitive. + @returns true only if the other array contains exactly the same strings with the same keys + */ + bool operator== (const StringPairArray& other) const; + + /** Compares two arrays. + Comparisons are case-sensitive. + @returns false if the other array contains exactly the same strings with the same keys + */ + bool operator!= (const StringPairArray& other) const; + + //============================================================================== + /** Finds the value corresponding to a key string. + + If no such key is found, this will just return an empty string. To check whether + a given key actually exists (because it might actually be paired with an empty string), use + the getAllKeys() method to obtain a list. + + Obviously the reference returned shouldn't be stored for later use, as the + string it refers to may disappear when the array changes. + + @see getValue + */ + const String& operator[] (const String& key) const; + + /** Finds the value corresponding to a key string. + + If no such key is found, this will just return the value provided as a default. + + @see operator[] + */ + String getValue (const String& key, const String& defaultReturnValue) const; + + + /** Returns a list of all keys in the array. */ + const StringArray& getAllKeys() const noexcept { return keys; } + + /** Returns a list of all values in the array. */ + const StringArray& getAllValues() const noexcept { return values; } + + /** Returns the number of strings in the array */ + inline int size() const noexcept { return keys.size(); }; + + + //============================================================================== + /** Adds or amends a key/value pair. + + If a value already exists with this key, its value will be overwritten, + otherwise the key/value pair will be added to the array. + */ + void set (const String& key, const String& value); + + /** Adds the items from another array to this one. + + This is equivalent to using set() to add each of the pairs from the other array. + */ + void addArray (const StringPairArray& other); + + //============================================================================== + /** Removes all elements from the array. */ + void clear(); + + /** Removes a string from the array based on its key. + + If the key isn't found, nothing will happen. + */ + void remove (const String& key); + + /** Removes a string from the array based on its index. + + If the index is out-of-range, no action will be taken. + */ + void remove (int index); + + //============================================================================== + /** Indicates whether to use a case-insensitive search when looking up a key string. + */ + void setIgnoresCase (bool shouldIgnoreCase); + + //============================================================================== + /** Returns a descriptive string containing the items. + This is handy for dumping the contents of an array. + */ + String getDescription() const; + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads(); + + +private: + //============================================================================== + StringArray keys, values; + bool ignoreCase; + + BEAST_LEAK_DETECTOR (StringPairArray) +}; + + +#endif // BEAST_STRINGPAIRARRAY_BEASTHEADER diff --git a/modules/beast_core/text/beast_StringPool.cpp b/modules/beast_core/text/beast_StringPool.cpp new file mode 100644 index 0000000000..b86188244f --- /dev/null +++ b/modules/beast_core/text/beast_StringPool.cpp @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +StringPool::StringPool() noexcept {} +StringPool::~StringPool() {} + +namespace StringPoolHelpers +{ + template + String::CharPointerType getPooledStringFromArray (Array& strings, + StringType newString, + const CriticalSection& lock) + { + const ScopedLock sl (lock); + int start = 0; + int end = strings.size(); + + for (;;) + { + if (start >= end) + { + bassert (start <= end); + strings.insert (start, newString); + return strings.getReference (start).getCharPointer(); + } + else + { + const String& startString = strings.getReference (start); + + if (startString == newString) + return startString.getCharPointer(); + + const int halfway = (start + end) >> 1; + + if (halfway == start) + { + if (startString.compare (newString) < 0) + ++start; + + strings.insert (start, newString); + return strings.getReference (start).getCharPointer(); + } + + const int comp = strings.getReference (halfway).compare (newString); + + if (comp == 0) + return strings.getReference (halfway).getCharPointer(); + else if (comp < 0) + start = halfway; + else + end = halfway; + } + } + } +} + +String::CharPointerType StringPool::getPooledString (const String& s) +{ + if (s.isEmpty()) + return String::empty.getCharPointer(); + + return StringPoolHelpers::getPooledStringFromArray (strings, s, lock); +} + +String::CharPointerType StringPool::getPooledString (const char* const s) +{ + if (s == nullptr || *s == 0) + return String::empty.getCharPointer(); + + return StringPoolHelpers::getPooledStringFromArray (strings, s, lock); +} + +String::CharPointerType StringPool::getPooledString (const wchar_t* const s) +{ + if (s == nullptr || *s == 0) + return String::empty.getCharPointer(); + + return StringPoolHelpers::getPooledStringFromArray (strings, s, lock); +} + +int StringPool::size() const noexcept +{ + return strings.size(); +} + +String::CharPointerType StringPool::operator[] (const int index) const noexcept +{ + return strings [index].getCharPointer(); +} diff --git a/modules/beast_core/text/beast_StringPool.h b/modules/beast_core/text/beast_StringPool.h new file mode 100644 index 0000000000..d991209429 --- /dev/null +++ b/modules/beast_core/text/beast_StringPool.h @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_STRINGPOOL_BEASTHEADER +#define BEAST_STRINGPOOL_BEASTHEADER + +#include "beast_String.h" +#include "../containers/beast_Array.h" + + +//============================================================================== +/** + A StringPool holds a set of shared strings, which reduces storage overheads and improves + comparison speed when dealing with many duplicate strings. + + When you add a string to a pool using getPooledString, it'll return a character + array containing the same string. This array is owned by the pool, and the same array + is returned every time a matching string is asked for. This means that it's trivial to + compare two pooled strings for equality, as you can simply compare their pointers. It + also cuts down on storage if you're using many copies of the same string. +*/ +class BEAST_API StringPool +{ +public: + //============================================================================== + /** Creates an empty pool. */ + StringPool() noexcept; + + /** Destructor */ + ~StringPool(); + + //============================================================================== + /** Returns a pointer to a copy of the string that is passed in. + + The pool will always return the same pointer when asked for a string that matches it. + The pool will own all the pointers that it returns, deleting them when the pool itself + is deleted. + */ + String::CharPointerType getPooledString (const String& original); + + /** Returns a pointer to a copy of the string that is passed in. + + The pool will always return the same pointer when asked for a string that matches it. + The pool will own all the pointers that it returns, deleting them when the pool itself + is deleted. + */ + String::CharPointerType getPooledString (const char* original); + + /** Returns a pointer to a copy of the string that is passed in. + + The pool will always return the same pointer when asked for a string that matches it. + The pool will own all the pointers that it returns, deleting them when the pool itself + is deleted. + */ + String::CharPointerType getPooledString (const wchar_t* original); + + //============================================================================== + /** Returns the number of strings in the pool. */ + int size() const noexcept; + + /** Returns one of the strings in the pool, by index. */ + String::CharPointerType operator[] (int index) const noexcept; + +private: + Array strings; + CriticalSection lock; +}; + + +#endif // BEAST_STRINGPOOL_BEASTHEADER diff --git a/modules/beast_core/text/beast_TextDiff.cpp b/modules/beast_core/text/beast_TextDiff.cpp new file mode 100644 index 0000000000..5739da32ea --- /dev/null +++ b/modules/beast_core/text/beast_TextDiff.cpp @@ -0,0 +1,236 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +struct TextDiffHelpers +{ + enum { minLengthToMatch = 3 }; + + struct StringRegion + { + StringRegion (const String& s) noexcept + : text (s.getCharPointer()), start (0), length (s.length()) {} + + StringRegion (const String::CharPointerType& t, int s, int len) noexcept + : text (t), start (s), length (len) {} + + String::CharPointerType text; + int start, length; + }; + + static void addInsertion (TextDiff& td, const String::CharPointerType& text, int index, int length) + { + TextDiff::Change c; + c.insertedText = String (text, (size_t) length); + c.start = index; + c.length = length; + td.changes.add (c); + } + + static void addDeletion (TextDiff& td, int index, int length) + { + TextDiff::Change c; + c.start = index; + c.length = length; + td.changes.add (c); + } + + static void diffSkippingCommonStart (TextDiff& td, const StringRegion& a, const StringRegion& b) + { + String::CharPointerType sa (a.text); + String::CharPointerType sb (b.text); + const int maxLen = bmax (a.length, b.length); + + for (int i = 0; i < maxLen; ++i, ++sa, ++sb) + { + if (*sa != *sb) + { + diffRecursively (td, StringRegion (sa, a.start + i, a.length - i), + StringRegion (sb, b.start + i, b.length - i)); + break; + } + } + } + + static void diffRecursively (TextDiff& td, const StringRegion& a, const StringRegion& b) + { + int indexA, indexB; + const int len = findLongestCommonSubstring (a.text, a.length, + b.text, b.length, + indexA, indexB); + + if (len >= minLengthToMatch) + { + if (indexA > 0 && indexB > 0) + diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA), + StringRegion (b.text, b.start, indexB)); + else if (indexA > 0) + addDeletion (td, b.start, indexA); + else if (indexB > 0) + addInsertion (td, b.text, b.start, indexB); + + diffRecursively (td, StringRegion (a.text + indexA + len, a.start + indexA + len, a.length - indexA - len), + StringRegion (b.text + indexB + len, b.start + indexB + len, b.length - indexB - len)); + } + else + { + if (a.length > 0) addDeletion (td, b.start, a.length); + if (b.length > 0) addInsertion (td, b.text, b.start, b.length); + } + } + + static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, + const String::CharPointerType& b, const int lenB, + int& indexInA, int& indexInB) + { + if (lenA == 0 || lenB == 0) + return 0; + + HeapBlock lines; + lines.calloc (2 + 2 * (size_t) lenB); + + int* l0 = lines; + int* l1 = l0 + lenB + 1; + + int bestLength = 0; + indexInA = indexInB = 0; + + for (int i = 0; i < lenA; ++i) + { + const beast_wchar ca = a.getAndAdvance(); + String::CharPointerType b2 (b); + + for (int j = 0; j < lenB; ++j) + { + if (ca != b2.getAndAdvance()) + { + l1[j + 1] = 0; + } + else + { + const int len = l0[j] + 1; + l1[j + 1] = len; + + if (len > bestLength) + { + bestLength = len; + indexInA = i; + indexInB = j; + } + } + } + + std::swap (l0, l1); + } + + indexInA -= bestLength - 1; + indexInB -= bestLength - 1; + return bestLength; + } +}; + +TextDiff::TextDiff (const String& original, const String& target) +{ + TextDiffHelpers::diffSkippingCommonStart (*this, original, target); +} + +String TextDiff::appliedTo (String text) const +{ + for (int i = 0; i < changes.size(); ++i) + text = changes.getReference(i).appliedTo (text); + + return text; +} + +bool TextDiff::Change::isDeletion() const noexcept +{ + return insertedText.isEmpty(); +} + +String TextDiff::Change::appliedTo (const String& text) const noexcept +{ + return text.substring (0, start) + (isDeletion() ? text.substring (start + length) + : (insertedText + text.substring (start))); +} + +//============================================================================== +//============================================================================== +#if BEAST_UNIT_TESTS + +class DiffTests : public UnitTest +{ +public: + DiffTests() : UnitTest ("TextDiff class") {} + + static String createString() + { + beast_wchar buffer[50] = { 0 }; + Random r; + + for (int i = r.nextInt (49); --i >= 0;) + { + if (r.nextInt (10) == 0) + { + do + { + buffer[i] = (beast_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (beast_wchar) ('a' + r.nextInt (3)); + } + + return CharPointer_UTF32 (buffer); + } + + void testDiff (const String& a, const String& b) + { + TextDiff diff (a, b); + const String result (diff.appliedTo (a)); + expectEquals (result, b); + } + + void runTest() + { + beginTest ("TextDiff"); + + testDiff (String::empty, String::empty); + testDiff ("x", String::empty); + testDiff (String::empty, "x"); + testDiff ("x", "x"); + testDiff ("x", "y"); + testDiff ("xxx", "x"); + testDiff ("x", "xxx"); + + for (int i = 5000; --i >= 0;) + { + String s (createString()); + testDiff (s, createString()); + testDiff (s + createString(), s + createString()); + } + } +}; + +static DiffTests diffTests; + +#endif diff --git a/modules/beast_core/text/beast_TextDiff.h b/modules/beast_core/text/beast_TextDiff.h new file mode 100644 index 0000000000..514c8d0c41 --- /dev/null +++ b/modules/beast_core/text/beast_TextDiff.h @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_TEXTDIFF_BEASTHEADER +#define BEAST_TEXTDIFF_BEASTHEADER + + +/** + Calculates and applies a sequence of changes to convert one text string into + another. + + Once created, the TextDiff object contains an array of change objects, where + each change can be either an insertion or a deletion. When applied in order + to the original string, these changes will convert it to the target string. +*/ +class BEAST_API TextDiff +{ +public: + /** Creates a set of diffs for converting the original string into the target. */ + TextDiff (const String& original, + const String& target); + + /** Applies this sequence of changes to the original string, producing the + target string that was specified when generating them. + + Obviously it only makes sense to call this function with the string that + was originally passed to the constructor. Any other input will produce an + undefined result. + */ + String appliedTo (String text) const; + + /** Describes a change, which can be either an insertion or deletion. */ + struct Change + { + String insertedText; /**< If this change is a deletion, this string will be empty; otherwise, + it'll be the text that should be inserted at the index specified by start. */ + int start; /**< Specifies the character index in a string at which text should be inserted or deleted. */ + int length; /**< If this change is a deletion, this specifies the number of characters to delete. For an + insertion, this is the length of the new text being inserted. */ + + /** Returns true if this change is a deletion, or false for an insertion. */ + bool isDeletion() const noexcept; + + /** Returns the result of applying this change to a string. */ + String appliedTo (const String& original) const noexcept; + }; + + /** The list of changes required to perform the transformation. + Applying each of these, in order, to the original string will produce the target. + */ + Array changes; +}; + + +#endif // BEAST_TEXTDIFF_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ChildProcess.cpp b/modules/beast_core/threads/beast_ChildProcess.cpp new file mode 100644 index 0000000000..49a928a236 --- /dev/null +++ b/modules/beast_core/threads/beast_ChildProcess.cpp @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +ChildProcess::ChildProcess() {} +ChildProcess::~ChildProcess() {} + +bool ChildProcess::waitForProcessToFinish (const int timeoutMs) const +{ + const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs; + + do + { + if (! isRunning()) + return true; + } + while (timeoutMs < 0 || Time::getMillisecondCounter() < timeoutTime); + + return false; +} + +String ChildProcess::readAllProcessOutput() +{ + MemoryOutputStream result; + + for (;;) + { + char buffer [512]; + const int num = readProcessOutput (buffer, sizeof (buffer)); + + if (num <= 0) + break; + + result.write (buffer, (size_t) num); + } + + return result.toString(); +} + +//============================================================================== +#if BEAST_UNIT_TESTS + +class ChildProcessTests : public UnitTest +{ +public: + ChildProcessTests() : UnitTest ("ChildProcess") {} + + void runTest() + { + beginTest ("Child Processes"); + + #if BEAST_WINDOWS || BEAST_MAC || BEAST_LINUX + ChildProcess p; + + #if BEAST_WINDOWS + expect (p.start ("tasklist")); + #else + expect (p.start ("ls /")); + #endif + + //String output (p.readAllProcessOutput()); + //expect (output.isNotEmpty()); + #endif + } +}; + +static ChildProcessTests childProcessUnitTests; + +#endif diff --git a/modules/beast_core/threads/beast_ChildProcess.h b/modules/beast_core/threads/beast_ChildProcess.h new file mode 100644 index 0000000000..4875efd608 --- /dev/null +++ b/modules/beast_core/threads/beast_ChildProcess.h @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CHILDPROCESS_BEASTHEADER +#define BEAST_CHILDPROCESS_BEASTHEADER + + +//============================================================================== +/** + Launches and monitors a child process. + + This class lets you launch an executable, and read its output. You can also + use it to check whether the child process has finished. +*/ +class BEAST_API ChildProcess +{ +public: + //============================================================================== + /** Creates a process object. + To actually launch the process, use start(). + */ + ChildProcess(); + + /** Destructor. + Note that deleting this object won't terminate the child process. + */ + ~ChildProcess(); + + /** Attempts to launch a child process command. + + The command should be the name of the executable file, followed by any arguments + that are required. + If the process has already been launched, this will launch it again. If a problem + occurs, the method will return false. + */ + bool start (const String& command); + + /** Attempts to launch a child process command. + + The first argument should be the name of the executable file, followed by any other + arguments that are needed. + If the process has already been launched, this will launch it again. If a problem + occurs, the method will return false. + */ + bool start (const StringArray& arguments); + + /** Returns true if the child process is alive. */ + bool isRunning() const; + + /** Attempts to read some output from the child process. + This will attempt to read up to the given number of bytes of data from the + process. It returns the number of bytes that were actually read. + */ + int readProcessOutput (void* destBuffer, int numBytesToRead); + + /** Blocks until the process has finished, and then returns its complete output + as a string. + */ + String readAllProcessOutput(); + + /** Blocks until the process is no longer running. */ + bool waitForProcessToFinish (int timeoutMs) const; + + /** Attempts to kill the child process. + Returns true if it succeeded. Trying to read from the process after calling this may + result in undefined behaviour. + */ + bool kill(); + +private: + //============================================================================== + class ActiveProcess; + friend class ScopedPointer; + ScopedPointer activeProcess; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess) +}; + + +#endif // BEAST_CHILDPROCESS_BEASTHEADER diff --git a/modules/beast_core/threads/beast_CriticalSection.h b/modules/beast_core/threads/beast_CriticalSection.h new file mode 100644 index 0000000000..d0da916418 --- /dev/null +++ b/modules/beast_core/threads/beast_CriticalSection.h @@ -0,0 +1,252 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_CRITICALSECTION_BEASTHEADER +#define BEAST_CRITICALSECTION_BEASTHEADER + +#include "beast_ScopedLock.h" + + +//============================================================================== +/** + A re-entrant mutex. + + A CriticalSection acts as a re-entrant mutex object. The best way to lock and unlock + one of these is by using RAII in the form of a local ScopedLock object - have a look + through the codebase for many examples of how to do this. + + @see ScopedLock, ScopedTryLock, ScopedUnlock, SpinLock, ReadWriteLock, Thread, InterProcessLock +*/ +class BEAST_API CriticalSection +{ +public: + //============================================================================== + /** Creates a CriticalSection object. */ + CriticalSection() noexcept; + + /** Destructor. + If the critical section is deleted whilst locked, any subsequent behaviour + is unpredictable. + */ + ~CriticalSection() noexcept; + + //============================================================================== + /** Acquires the lock. + + If the lock is already held by the caller thread, the method returns immediately. + If the lock is currently held by another thread, this will wait until it becomes free. + + It's strongly recommended that you never call this method directly - instead use the + ScopedLock class to manage the locking using an RAII pattern instead. + + @see exit, tryEnter, ScopedLock + */ + void enter() const noexcept; + + /** Attempts to lock this critical section without blocking. + + This method behaves identically to CriticalSection::enter, except that the caller thread + does not wait if the lock is currently held by another thread but returns false immediately. + + @returns false if the lock is currently held by another thread, true otherwise. + @see enter + */ + bool tryEnter() const noexcept; + + /** Releases the lock. + + If the caller thread hasn't got the lock, this can have unpredictable results. + + If the enter() method has been called multiple times by the thread, each + call must be matched by a call to exit() before other threads will be allowed + to take over the lock. + + @see enter, ScopedLock + */ + void exit() const noexcept; + + + //============================================================================== + /** Provides the type of scoped lock to use with a CriticalSection. */ + typedef GenericScopedLock ScopedLockType; + + /** Provides the type of scoped unlocker to use with a CriticalSection. */ + typedef GenericScopedUnlock ScopedUnlockType; + + /** Provides the type of scoped try-locker to use with a CriticalSection. */ + typedef GenericScopedTryLock ScopedTryLockType; + + +private: + //============================================================================== + #if BEAST_WINDOWS + // To avoid including windows.h in the public BEAST headers, we'll just allocate a + // block of memory here that's big enough to be used internally as a windows critical + // section structure. + #if BEAST_64BIT + uint8 internal [44]; + #else + uint8 internal [24]; + #endif + #else + mutable pthread_mutex_t internal; + #endif + + BEAST_DECLARE_NON_COPYABLE (CriticalSection) +}; + + +//============================================================================== +/** + A class that can be used in place of a real CriticalSection object, but which + doesn't perform any locking. + + This is currently used by some templated classes, and most compilers should + manage to optimise it out of existence. + + @see CriticalSection, Array, OwnedArray, ReferenceCountedArray +*/ +class BEAST_API DummyCriticalSection +{ +public: + inline DummyCriticalSection() noexcept {} + inline ~DummyCriticalSection() noexcept {} + + inline void enter() const noexcept {} + inline bool tryEnter() const noexcept { return true; } + inline void exit() const noexcept {} + + //============================================================================== + /** A dummy scoped-lock type to use with a dummy critical section. */ + struct ScopedLockType + { + ScopedLockType (const DummyCriticalSection&) noexcept {} + }; + + /** A dummy scoped-unlocker type to use with a dummy critical section. */ + typedef ScopedLockType ScopedUnlockType; + +private: + BEAST_DECLARE_NON_COPYABLE (DummyCriticalSection) +}; + +//============================================================================== +/** + Automatically locks and unlocks a CriticalSection object. + + Use one of these as a local variable to provide RAII-based locking of a CriticalSection. + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const ScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ...do some stuff... + + // myCriticalSection gets unlocked here. + } + @endcode + + @see CriticalSection, ScopedUnlock +*/ +typedef CriticalSection::ScopedLockType ScopedLock; + +//============================================================================== +/** + Automatically unlocks and re-locks a CriticalSection object. + + This is the reverse of a ScopedLock object - instead of locking the critical + section for the lifetime of this object, it unlocks it. + + Make sure you don't try to unlock critical sections that aren't actually locked! + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const ScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ... do some stuff with it locked .. + + while (xyz) + { + ... do some stuff with it locked .. + + const ScopedUnlock unlocker (myCriticalSection); + + // myCriticalSection is now unlocked for the remainder of this block, + // and re-locked at the end. + + ...do some stuff with it unlocked ... + } + + // myCriticalSection gets unlocked here. + } + @endcode + + @see CriticalSection, ScopedLock +*/ +typedef CriticalSection::ScopedUnlockType ScopedUnlock; + +//============================================================================== +/** + Automatically tries to lock and unlock a CriticalSection object. + + Use one of these as a local variable to control access to a CriticalSection. + + e.g. @code + CriticalSection myCriticalSection; + + for (;;) + { + const ScopedTryLock myScopedTryLock (myCriticalSection); + + // Unlike using a ScopedLock, this may fail to actually get the lock, so you + // should test this with the isLocked() method before doing your thread-unsafe + // action.. + if (myScopedTryLock.isLocked()) + { + ...do some stuff... + } + else + { + ..our attempt at locking failed because another thread had already locked it.. + } + + // myCriticalSection gets unlocked here (if it was locked) + } + @endcode + + @see CriticalSection::tryEnter, ScopedLock, ScopedUnlock, ScopedReadLock +*/ +typedef CriticalSection::ScopedTryLockType ScopedTryLock; + + +#endif // BEAST_CRITICALSECTION_BEASTHEADER diff --git a/modules/beast_core/threads/beast_DynamicLibrary.h b/modules/beast_core/threads/beast_DynamicLibrary.h new file mode 100644 index 0000000000..9b99c6a8a3 --- /dev/null +++ b/modules/beast_core/threads/beast_DynamicLibrary.h @@ -0,0 +1,80 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_DYNAMICLIBRARY_BEASTHEADER +#define BEAST_DYNAMICLIBRARY_BEASTHEADER + +/** + Handles the opening and closing of DLLs. + + This class can be used to open a DLL and get some function pointers from it. + Since the DLL is freed when this object is deleted, it's handy for managing + library lifetimes using RAII. +*/ +class BEAST_API DynamicLibrary +{ +public: + /** Creates an unopened DynamicLibrary object. + Call open() to actually open one. + */ + DynamicLibrary() noexcept : handle (nullptr) {} + + /** + */ + DynamicLibrary (const String& name) : handle (nullptr) { open (name); } + + /** Destructor. + If a library is currently open, it will be closed when this object is destroyed. + */ + ~DynamicLibrary() { close(); } + + /** Opens a DLL. + The name and the method by which it gets found is of course platform-specific, and + may or may not include a path, depending on the OS. + If a library is already open when this method is called, it will first close the library + before attempting to load the new one. + @returns true if the library was successfully found and opened. + */ + bool open (const String& name); + + /** Releases the currently-open DLL, or has no effect if none was open. */ + void close(); + + /** Tries to find a named function in the currently-open DLL, and returns a pointer to it. + If no library is open, or if the function isn't found, this will return a null pointer. + */ + void* getFunction (const String& functionName) noexcept; + + /** Returns the platform-specific native library handle. + You'll need to cast this to whatever is appropriate for the OS that's in use. + */ + void* getNativeHandle() const noexcept { return handle; } + +private: + void* handle; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DynamicLibrary) +}; + + +#endif // BEAST_DYNAMICLIBRARY_BEASTHEADER diff --git a/modules/beast_core/threads/beast_HighResolutionTimer.cpp b/modules/beast_core/threads/beast_HighResolutionTimer.cpp new file mode 100644 index 0000000000..68cd34988d --- /dev/null +++ b/modules/beast_core/threads/beast_HighResolutionTimer.cpp @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +HighResolutionTimer::HighResolutionTimer() { pimpl = new Pimpl (*this); } +HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } + +void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (bmax (1, periodMs)); } +void HighResolutionTimer::stopTimer() { pimpl->stop(); } + +bool HighResolutionTimer::isTimerRunning() const noexcept { return pimpl->periodMs != 0; } +int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->periodMs; } diff --git a/modules/beast_core/threads/beast_HighResolutionTimer.h b/modules/beast_core/threads/beast_HighResolutionTimer.h new file mode 100644 index 0000000000..a42674f911 --- /dev/null +++ b/modules/beast_core/threads/beast_HighResolutionTimer.h @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_HIGHRESOLUTIONTIMER_BEASTHEADER +#define BEAST_HIGHRESOLUTIONTIMER_BEASTHEADER + +/** + A high-resolution periodic timer. + + This provides accurately-timed regular callbacks. Unlike the normal Timer + class, this one uses a dedicated thread, not the message thread, so is + far more stable and precise. + + You should only use this class in situations where you really need accuracy, + because unlike the normal Timer class, which is very lightweight and cheap + to start/stop, the HighResolutionTimer will use far more resources, and + starting/stopping it may involve launching and killing threads. + + @see Timer +*/ +class BEAST_API HighResolutionTimer +{ +protected: + /** Creates a HighResolutionTimer. + When created, the timer is stopped, so use startTimer() to get it going. + */ + HighResolutionTimer(); + +public: + /** Destructor. */ + virtual ~HighResolutionTimer(); + + //============================================================================== + /** The user-defined callback routine that actually gets called periodically. + + This will be called on a dedicated timer thread, so make sure your + implementation is thread-safe! + + It's perfectly ok to call startTimer() or stopTimer() from within this + callback to change the subsequent intervals. + */ + virtual void hiResTimerCallback() = 0; + + //============================================================================== + /** Starts the timer and sets the length of interval required. + + If the timer is already started, this will reset its counter, so the + time between calling this method and the next timer callback will not be + less than the interval length passed in. + + @param intervalInMilliseconds the interval to use (any values less than 1 will be + rounded up to 1) + */ + void startTimer (int intervalInMilliseconds); + + /** Stops the timer. + + This method may block while it waits for pending callbacks to complete. Once it + returns, no more callbacks will be made. If it is called from the timer's own thread, + it will cancel the timer after the current callback returns. + */ + void stopTimer(); + + /** Checks if the timer has been started. + @returns true if the timer is running. + */ + bool isTimerRunning() const noexcept; + + /** Returns the timer's interval. + @returns the timer's interval in milliseconds if it's running, or 0 if it's not. + */ + int getTimerInterval() const noexcept; + +private: + struct Pimpl; + friend struct Pimpl; + friend class ScopedPointer; + ScopedPointer pimpl; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) +}; + + +#endif // BEAST_HIGHRESOLUTIONTIMER_BEASTHEADER diff --git a/modules/beast_core/threads/beast_InterProcessLock.h b/modules/beast_core/threads/beast_InterProcessLock.h new file mode 100644 index 0000000000..946f29580d --- /dev/null +++ b/modules/beast_core/threads/beast_InterProcessLock.h @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_INTERPROCESSLOCK_BEASTHEADER +#define BEAST_INTERPROCESSLOCK_BEASTHEADER + +#include "../text/beast_String.h" +#include "../memory/beast_ScopedPointer.h" + + +//============================================================================== +/** + Acts as a critical section which processes can use to block each other. + + @see CriticalSection +*/ +class BEAST_API InterProcessLock +{ +public: + //============================================================================== + /** Creates a lock object. + @param name a name that processes will use to identify this lock object + */ + explicit InterProcessLock (const String& name); + + /** Destructor. + This will also release the lock if it's currently held by this process. + */ + ~InterProcessLock(); + + //============================================================================== + /** Attempts to lock the critical section. + + @param timeOutMillisecs how many milliseconds to wait if the lock is already + held by another process - a value of 0 will return + immediately, negative values will wait forever + @returns true if the lock could be gained within the timeout period, or + false if the timeout expired. + */ + bool enter (int timeOutMillisecs = -1); + + /** Releases the lock if it's currently held by this process. */ + void exit(); + + //============================================================================== + /** + Automatically locks and unlocks an InterProcessLock object. + + This works like a ScopedLock, but using an InterprocessLock rather than + a CriticalSection. + + @see ScopedLock + */ + class ScopedLockType + { + public: + //============================================================================== + /** Creates a scoped lock. + + As soon as it is created, this will lock the InterProcessLock, and + when the ScopedLockType object is deleted, the InterProcessLock will + be unlocked. + + Note that since an InterprocessLock can fail due to errors, you should check + isLocked() to make sure that the lock was successful before using it. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + explicit ScopedLockType (InterProcessLock& l) : ipLock (l) { lockWasSuccessful = l.enter(); } + + /** Destructor. + + The InterProcessLock will be unlocked when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~ScopedLockType() { ipLock.exit(); } + + /** Returns true if the InterProcessLock was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + + private: + //============================================================================== + InterProcessLock& ipLock; + bool lockWasSuccessful; + + BEAST_DECLARE_NON_COPYABLE (ScopedLockType) + }; + +private: + //============================================================================== + class Pimpl; + friend class ScopedPointer ; + ScopedPointer pimpl; + + CriticalSection lock; + String name; + + BEAST_DECLARE_NON_COPYABLE (InterProcessLock) +}; + + +#endif // BEAST_INTERPROCESSLOCK_BEASTHEADER diff --git a/modules/beast_core/threads/beast_Process.h b/modules/beast_core/threads/beast_Process.h new file mode 100644 index 0000000000..05a9f1f367 --- /dev/null +++ b/modules/beast_core/threads/beast_Process.h @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_PROCESS_BEASTHEADER +#define BEAST_PROCESS_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** Represents the current executable's process. + + This contains methods for controlling the current application at the + process-level. + + @see Thread, BEASTApplication +*/ +class BEAST_API Process +{ +public: + //============================================================================== + enum ProcessPriority + { + LowPriority = 0, + NormalPriority = 1, + HighPriority = 2, + RealtimePriority = 3 + }; + + /** Changes the current process's priority. + + @param priority the process priority, where + 0=low, 1=normal, 2=high, 3=realtime + */ + static void setPriority (const ProcessPriority priority); + + /** Kills the current process immediately. + + This is an emergency process terminator that kills the application + immediately - it's intended only for use only when something goes + horribly wrong. + + @see BEASTApplication::quit + */ + static void terminate(); + + //============================================================================== + /** Returns true if this application process is the one that the user is + currently using. + */ + static bool isForegroundProcess(); + + /** Attempts to make the current process the active one. + (This is not possible on some platforms). + */ + static void makeForegroundProcess(); + + //============================================================================== + /** Raises the current process's privilege level. + + Does nothing if this isn't supported by the current OS, or if process + privilege level is fixed. + */ + static void raisePrivilege(); + + /** Lowers the current process's privilege level. + + Does nothing if this isn't supported by the current OS, or if process + privilege level is fixed. + */ + static void lowerPrivilege(); + + //============================================================================== + /** Returns true if this process is being hosted by a debugger. */ + static bool BEAST_CALLTYPE isRunningUnderDebugger(); + + + //============================================================================== + /** Tries to launch the OS's default reader application for a given file or URL. */ + static bool openDocument (const String& documentURL, const String& parameters); + + /** Tries to launch the OS's default email application to let the user create a message. */ + static bool openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach); + + #if BEAST_WINDOWS || DOXYGEN + //============================================================================== + /** WINDOWS ONLY - This returns the HINSTANCE of the current module. + + The return type is a void* to avoid being dependent on windows.h - just cast + it to a HINSTANCE to use it. + + In a normal BEAST application, this will be automatically set to the module + handle of the executable. + + If you've built a DLL and plan to use any BEAST messaging or windowing classes, + you'll need to make sure you call the setCurrentModuleInstanceHandle() + to provide the correct module handle in your DllMain() function, because + the system relies on the correct instance handle when opening windows. + */ + static void* BEAST_CALLTYPE getCurrentModuleInstanceHandle() noexcept; + + /** WINDOWS ONLY - Sets a new module handle to be used by the library. + + The parameter type is a void* to avoid being dependent on windows.h, but it actually + expects a HINSTANCE value. + + @see getCurrentModuleInstanceHandle() + */ + static void BEAST_CALLTYPE setCurrentModuleInstanceHandle (void* newHandle) noexcept; + #endif + + #if BEAST_MAC || DOXYGEN + //============================================================================== + /** OSX ONLY - Shows or hides the OSX dock icon for this app. */ + static void setDockIconVisible (bool isVisible); + #endif + +private: + Process(); + BEAST_DECLARE_NON_COPYABLE (Process) +}; + + +#endif // BEAST_PROCESS_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ReadWriteLock.cpp b/modules/beast_core/threads/beast_ReadWriteLock.cpp new file mode 100644 index 0000000000..13fcaecefb --- /dev/null +++ b/modules/beast_core/threads/beast_ReadWriteLock.cpp @@ -0,0 +1,153 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +ReadWriteLock::ReadWriteLock() noexcept + : numWaitingWriters (0), + numWriters (0), + writerThreadId (0) +{ + readerThreads.ensureStorageAllocated (16); +} + +ReadWriteLock::~ReadWriteLock() noexcept +{ + bassert (readerThreads.size() == 0); + bassert (numWriters == 0); +} + +//============================================================================== +void ReadWriteLock::enterRead() const noexcept +{ + while (! tryEnterRead()) + waitEvent.wait (100); +} + +bool ReadWriteLock::tryEnterRead() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + + const SpinLock::ScopedLockType sl (accessLock); + + for (int i = 0; i < readerThreads.size(); ++i) + { + ThreadRecursionCount& trc = readerThreads.getReference(i); + + if (trc.threadID == threadId) + { + trc.count++; + return true; + } + } + + if (numWriters + numWaitingWriters == 0 + || (threadId == writerThreadId && numWriters > 0)) + { + ThreadRecursionCount trc = { threadId, 1 }; + readerThreads.add (trc); + return true; + } + + return false; +} + +void ReadWriteLock::exitRead() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + const SpinLock::ScopedLockType sl (accessLock); + + for (int i = 0; i < readerThreads.size(); ++i) + { + ThreadRecursionCount& trc = readerThreads.getReference(i); + + if (trc.threadID == threadId) + { + if (--(trc.count) == 0) + { + readerThreads.remove (i); + waitEvent.signal(); + } + + return; + } + } + + jassertfalse; // unlocking a lock that wasn't locked.. +} + +//============================================================================== +void ReadWriteLock::enterWrite() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + const SpinLock::ScopedLockType sl (accessLock); + + for (;;) + { + if (readerThreads.size() + numWriters == 0 + || threadId == writerThreadId + || (readerThreads.size() == 1 + && readerThreads.getReference(0).threadID == threadId)) + { + writerThreadId = threadId; + ++numWriters; + break; + } + + ++numWaitingWriters; + accessLock.exit(); + waitEvent.wait (100); + accessLock.enter(); + --numWaitingWriters; + } +} + +bool ReadWriteLock::tryEnterWrite() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + const SpinLock::ScopedLockType sl (accessLock); + + if (readerThreads.size() + numWriters == 0 + || threadId == writerThreadId + || (readerThreads.size() == 1 + && readerThreads.getReference(0).threadID == threadId)) + { + writerThreadId = threadId; + ++numWriters; + return true; + } + + return false; +} + +void ReadWriteLock::exitWrite() const noexcept +{ + const SpinLock::ScopedLockType sl (accessLock); + + // check this thread actually had the lock.. + bassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); + + if (--numWriters == 0) + { + writerThreadId = 0; + waitEvent.signal(); + } +} diff --git a/modules/beast_core/threads/beast_ReadWriteLock.h b/modules/beast_core/threads/beast_ReadWriteLock.h new file mode 100644 index 0000000000..1b9d961e02 --- /dev/null +++ b/modules/beast_core/threads/beast_ReadWriteLock.h @@ -0,0 +1,154 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_READWRITELOCK_BEASTHEADER +#define BEAST_READWRITELOCK_BEASTHEADER + +#include "beast_CriticalSection.h" +#include "beast_SpinLock.h" +#include "beast_WaitableEvent.h" +#include "beast_Thread.h" +#include "../containers/beast_Array.h" + + +//============================================================================== +/** + A critical section that allows multiple simultaneous readers. + + Features of this type of lock are: + + - Multiple readers can hold the lock at the same time, but only one writer + can hold it at once. + - Writers trying to gain the lock will be blocked until all readers and writers + have released it + - Readers trying to gain the lock while a writer is waiting to acquire it will be + blocked until the writer has obtained and released it + - If a thread already has a read lock and tries to obtain a write lock, it will succeed if + there are no other readers + - If a thread already has the write lock and tries to obtain a read lock, this will succeed. + - Recursive locking is supported. + + @see ScopedReadLock, ScopedWriteLock, CriticalSection +*/ +class BEAST_API ReadWriteLock +{ +public: + //============================================================================== + /** + Creates a ReadWriteLock object. + */ + ReadWriteLock() noexcept; + + /** Destructor. + + If the object is deleted whilst locked, any subsequent behaviour + is unpredictable. + */ + ~ReadWriteLock() noexcept; + + //============================================================================== + /** Locks this object for reading. + + Multiple threads can simulaneously lock the object for reading, but if another + thread has it locked for writing, then this will block until it releases the + lock. + + @see exitRead, ScopedReadLock + */ + void enterRead() const noexcept; + + /** Tries to lock this object for reading. + + Multiple threads can simulaneously lock the object for reading, but if another + thread has it locked for writing, then this will fail and return false. + + @returns true if the lock is successfully gained. + @see exitRead, ScopedReadLock + */ + bool tryEnterRead() const noexcept; + + /** Releases the read-lock. + + If the caller thread hasn't got the lock, this can have unpredictable results. + + If the enterRead() method has been called multiple times by the thread, each + call must be matched by a call to exitRead() before other threads will be allowed + to take over the lock. + + @see enterRead, ScopedReadLock + */ + void exitRead() const noexcept; + + //============================================================================== + /** Locks this object for writing. + + This will block until any other threads that have it locked for reading or + writing have released their lock. + + @see exitWrite, ScopedWriteLock + */ + void enterWrite() const noexcept; + + /** Tries to lock this object for writing. + + This is like enterWrite(), but doesn't block - it returns true if it manages + to obtain the lock. + + @returns true if the lock is successfully gained. + @see enterWrite + */ + bool tryEnterWrite() const noexcept; + + /** Releases the write-lock. + + If the caller thread hasn't got the lock, this can have unpredictable results. + + If the enterWrite() method has been called multiple times by the thread, each + call must be matched by a call to exit() before other threads will be allowed + to take over the lock. + + @see enterWrite, ScopedWriteLock + */ + void exitWrite() const noexcept; + + +private: + //============================================================================== + SpinLock accessLock; + WaitableEvent waitEvent; + mutable int numWaitingWriters, numWriters; + mutable Thread::ThreadID writerThreadId; + + struct ThreadRecursionCount + { + Thread::ThreadID threadID; + int count; + }; + + mutable Array readerThreads; + + BEAST_DECLARE_NON_COPYABLE (ReadWriteLock) +}; + + +#endif // BEAST_READWRITELOCK_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ScopedLock.h b/modules/beast_core/threads/beast_ScopedLock.h new file mode 100644 index 0000000000..cd19d5df60 --- /dev/null +++ b/modules/beast_core/threads/beast_ScopedLock.h @@ -0,0 +1,232 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SCOPEDLOCK_BEASTHEADER +#define BEAST_SCOPEDLOCK_BEASTHEADER + + +//============================================================================== +/** + Automatically locks and unlocks a mutex object. + + Use one of these as a local variable to provide RAII-based locking of a mutex. + + The templated class could be a CriticalSection, SpinLock, or anything else that + provides enter() and exit() methods. + + e.g. @code + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ...do some stuff... + + // myCriticalSection gets unlocked here. + } + @endcode + + @see GenericScopedUnlock, CriticalSection, SpinLock, ScopedLock, ScopedUnlock +*/ +template +class GenericScopedLock +{ +public: + //============================================================================== + /** Creates a GenericScopedLock. + + As soon as it is created, this will acquire the lock, and when the GenericScopedLock + object is deleted, the lock will be released. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedLock (const LockType& lock) noexcept : lock_ (lock) { lock.enter(); } + + /** Destructor. + The lock will be released when the destructor is called. + Make sure this object is created and deleted by the same thread, otherwise there are + no guarantees what will happen! + */ + inline ~GenericScopedLock() noexcept { lock_.exit(); } + +private: + //============================================================================== + const LockType& lock_; + + BEAST_DECLARE_NON_COPYABLE (GenericScopedLock) +}; + + +//============================================================================== +/** + Automatically unlocks and re-locks a mutex object. + + This is the reverse of a GenericScopedLock object - instead of locking the mutex + for the lifetime of this object, it unlocks it. + + Make sure you don't try to unlock mutexes that aren't actually locked! + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ... do some stuff with it locked .. + + while (xyz) + { + ... do some stuff with it locked .. + + const GenericScopedUnlock unlocker (myCriticalSection); + + // myCriticalSection is now unlocked for the remainder of this block, + // and re-locked at the end. + + ...do some stuff with it unlocked ... + } + + // myCriticalSection gets unlocked here. + } + @endcode + + @see GenericScopedLock, CriticalSection, ScopedLock, ScopedUnlock +*/ +template +class GenericScopedUnlock +{ +public: + //============================================================================== + /** Creates a GenericScopedUnlock. + + As soon as it is created, this will unlock the CriticalSection, and + when the ScopedLock object is deleted, the CriticalSection will + be re-locked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedUnlock (const LockType& lock) noexcept : lock_ (lock) { lock.exit(); } + + /** Destructor. + + The CriticalSection will be unlocked when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~GenericScopedUnlock() noexcept { lock_.enter(); } + + +private: + //============================================================================== + const LockType& lock_; + + BEAST_DECLARE_NON_COPYABLE (GenericScopedUnlock) +}; + + +//============================================================================== +/** + Automatically locks and unlocks a mutex object. + + Use one of these as a local variable to provide RAII-based locking of a mutex. + + The templated class could be a CriticalSection, SpinLock, or anything else that + provides enter() and exit() methods. + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedTryLock myScopedTryLock (myCriticalSection); + + // Unlike using a ScopedLock, this may fail to actually get the lock, so you + // should test this with the isLocked() method before doing your thread-unsafe + // action.. + if (myScopedTryLock.isLocked()) + { + ...do some stuff... + } + else + { + ..our attempt at locking failed because another thread had already locked it.. + } + + // myCriticalSection gets unlocked here (if it was locked) + } + @endcode + + @see CriticalSection::tryEnter, GenericScopedLock, GenericScopedUnlock +*/ +template +class GenericScopedTryLock +{ +public: + //============================================================================== + /** Creates a GenericScopedTryLock. + + As soon as it is created, this will attempt to acquire the lock, and when the + GenericScopedTryLock is deleted, the lock will be released (if the lock was + successfully acquired). + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedTryLock (const LockType& lock) noexcept + : lock_ (lock), lockWasSuccessful (lock.tryEnter()) {} + + /** Destructor. + + The mutex will be unlocked (if it had been successfully locked) when the + destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~GenericScopedTryLock() noexcept { if (lockWasSuccessful) lock_.exit(); } + + /** Returns true if the mutex was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + +private: + //============================================================================== + const LockType& lock_; + const bool lockWasSuccessful; + + BEAST_DECLARE_NON_COPYABLE (GenericScopedTryLock) +}; + + +#endif // BEAST_SCOPEDLOCK_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ScopedReadLock.h b/modules/beast_core/threads/beast_ScopedReadLock.h new file mode 100644 index 0000000000..0ec698c6ae --- /dev/null +++ b/modules/beast_core/threads/beast_ScopedReadLock.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SCOPEDREADLOCK_BEASTHEADER +#define BEAST_SCOPEDREADLOCK_BEASTHEADER + +#include "beast_ReadWriteLock.h" + + +//============================================================================== +/** + Automatically locks and unlocks a ReadWriteLock object. + + Use one of these as a local variable to control access to a ReadWriteLock. + + e.g. @code + + ReadWriteLock myLock; + + for (;;) + { + const ScopedReadLock myScopedLock (myLock); + // myLock is now locked + + ...do some stuff... + + // myLock gets unlocked here. + } + @endcode + + @see ReadWriteLock, ScopedWriteLock +*/ +class BEAST_API ScopedReadLock +{ +public: + //============================================================================== + /** Creates a ScopedReadLock. + + As soon as it is created, this will call ReadWriteLock::enterRead(), and + when the ScopedReadLock object is deleted, the ReadWriteLock will + be unlocked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit ScopedReadLock (const ReadWriteLock& lock) noexcept : lock_ (lock) { lock.enterRead(); } + + /** Destructor. + + The ReadWriteLock's exitRead() method will be called when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~ScopedReadLock() noexcept { lock_.exitRead(); } + + +private: + //============================================================================== + const ReadWriteLock& lock_; + + BEAST_DECLARE_NON_COPYABLE (ScopedReadLock) +}; + + +#endif // BEAST_SCOPEDREADLOCK_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ScopedWriteLock.h b/modules/beast_core/threads/beast_ScopedWriteLock.h new file mode 100644 index 0000000000..fc0894fdb1 --- /dev/null +++ b/modules/beast_core/threads/beast_ScopedWriteLock.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SCOPEDWRITELOCK_BEASTHEADER +#define BEAST_SCOPEDWRITELOCK_BEASTHEADER + +#include "beast_ReadWriteLock.h" + + +//============================================================================== +/** + Automatically locks and unlocks a ReadWriteLock object. + + Use one of these as a local variable to control access to a ReadWriteLock. + + e.g. @code + + ReadWriteLock myLock; + + for (;;) + { + const ScopedWriteLock myScopedLock (myLock); + // myLock is now locked + + ...do some stuff... + + // myLock gets unlocked here. + } + @endcode + + @see ReadWriteLock, ScopedReadLock +*/ +class BEAST_API ScopedWriteLock +{ +public: + //============================================================================== + /** Creates a ScopedWriteLock. + + As soon as it is created, this will call ReadWriteLock::enterWrite(), and + when the ScopedWriteLock object is deleted, the ReadWriteLock will + be unlocked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit ScopedWriteLock (const ReadWriteLock& lock) noexcept : lock_ (lock) { lock.enterWrite(); } + + /** Destructor. + + The ReadWriteLock's exitWrite() method will be called when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~ScopedWriteLock() noexcept { lock_.exitWrite(); } + + +private: + //============================================================================== + const ReadWriteLock& lock_; + + BEAST_DECLARE_NON_COPYABLE (ScopedWriteLock) +}; + + +#endif // BEAST_SCOPEDWRITELOCK_BEASTHEADER diff --git a/modules/beast_core/threads/beast_SpinLock.h b/modules/beast_core/threads/beast_SpinLock.h new file mode 100644 index 0000000000..4d773a6042 --- /dev/null +++ b/modules/beast_core/threads/beast_SpinLock.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_SPINLOCK_BEASTHEADER +#define BEAST_SPINLOCK_BEASTHEADER + +#include "beast_ScopedLock.h" + + +//============================================================================== +/** + A simple spin-lock class that can be used as a simple, low-overhead mutex for + uncontended situations. + + Note that unlike a CriticalSection, this type of lock is not re-entrant, and may + be less efficient when used it a highly contended situation, but it's very small and + requires almost no initialisation. + It's most appropriate for simple situations where you're only going to hold the + lock for a very brief time. + + @see CriticalSection +*/ +class BEAST_API SpinLock +{ +public: + inline SpinLock() noexcept {} + inline ~SpinLock() noexcept {} + + /** Acquires the lock. + This will block until the lock has been successfully acquired by this thread. + Note that a SpinLock is NOT re-entrant, and is not smart enough to know whether the + caller thread already has the lock - so if a thread tries to acquire a lock that it + already holds, this method will never return! + + It's strongly recommended that you never call this method directly - instead use the + ScopedLockType class to manage the locking using an RAII pattern instead. + */ + void enter() const noexcept; + + /** Attempts to acquire the lock, returning true if this was successful. */ + inline bool tryEnter() const noexcept + { + return lock.compareAndSetBool (1, 0); + } + + /** Releases the lock. */ + inline void exit() const noexcept + { + bassert (lock.value == 1); // Agh! Releasing a lock that isn't currently held! + lock = 0; + } + + //============================================================================== + /** Provides the type of scoped lock to use for locking a SpinLock. */ + typedef GenericScopedLock ScopedLockType; + + /** Provides the type of scoped unlocker to use with a SpinLock. */ + typedef GenericScopedUnlock ScopedUnlockType; + +private: + //============================================================================== + mutable Atomic lock; + + BEAST_DECLARE_NON_COPYABLE (SpinLock) +}; + + +#endif // BEAST_SPINLOCK_BEASTHEADER diff --git a/modules/beast_core/threads/beast_Thread.cpp b/modules/beast_core/threads/beast_Thread.cpp new file mode 100644 index 0000000000..e344a5489d --- /dev/null +++ b/modules/beast_core/threads/beast_Thread.cpp @@ -0,0 +1,358 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +Thread::Thread (const String& threadName_) + : threadName (threadName_), + threadHandle (nullptr), + threadId (0), + threadPriority (5), + affinityMask (0), + shouldExit (false) +{ +} + +Thread::~Thread() +{ + /* If your thread class's destructor has been called without first stopping the thread, that + means that this partially destructed object is still performing some work - and that's + probably a Bad Thing! + + To avoid this type of nastiness, always make sure you call stopThread() before or during + your subclass's destructor. + */ + bassert (! isThreadRunning()); + + stopThread (100); +} + +//============================================================================== +// Use a ref-counted object to hold this shared data, so that it can outlive its static +// shared pointer when threads are still running during static shutdown. +struct CurrentThreadHolder : public ReferenceCountedObject +{ + CurrentThreadHolder() noexcept {} + + typedef ReferenceCountedObjectPtr Ptr; + ThreadLocalValue value; + + BEAST_DECLARE_NON_COPYABLE (CurrentThreadHolder) +}; + +static char currentThreadHolderLock [sizeof (SpinLock)]; // (statically initialised to zeros). + +static CurrentThreadHolder::Ptr getCurrentThreadHolder() +{ + static CurrentThreadHolder::Ptr currentThreadHolder; + SpinLock::ScopedLockType lock (*reinterpret_cast (currentThreadHolderLock)); + + if (currentThreadHolder == nullptr) + currentThreadHolder = new CurrentThreadHolder(); + + return currentThreadHolder; +} + +void Thread::threadEntryPoint() +{ + const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder()); + currentThreadHolder->value = this; + + BEAST_TRY + { + if (threadName.isNotEmpty()) + setCurrentThreadName (threadName); + + if (startSuspensionEvent.wait (10000)) + { + bassert (getCurrentThreadId() == threadId); + + if (affinityMask != 0) + setCurrentThreadAffinityMask (affinityMask); + + run(); + } + } + BEAST_CATCH_ALL_ASSERT + + currentThreadHolder->value.releaseCurrentThreadStorage(); + closeThreadHandle(); +} + +// used to wrap the incoming call from the platform-specific code +void BEAST_API beast_threadEntryPoint (void* userData) +{ + static_cast (userData)->threadEntryPoint(); +} + +//============================================================================== +void Thread::startThread() +{ + const ScopedLock sl (startStopLock); + + shouldExit = false; + + if (threadHandle == nullptr) + { + launchThread(); + setThreadPriority (threadHandle, threadPriority); + startSuspensionEvent.signal(); + } +} + +void Thread::startThread (const int priority) +{ + const ScopedLock sl (startStopLock); + + if (threadHandle == nullptr) + { + threadPriority = priority; + startThread(); + } + else + { + setPriority (priority); + } +} + +bool Thread::isThreadRunning() const +{ + return threadHandle != nullptr; +} + +Thread* Thread::getCurrentThread() +{ + return getCurrentThreadHolder()->value.get(); +} + +//============================================================================== +void Thread::signalThreadShouldExit() +{ + shouldExit = true; +} + +bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const +{ + // Doh! So how exactly do you expect this thread to wait for itself to stop?? + bassert (getThreadId() != getCurrentThreadId() || getCurrentThreadId() == 0); + + const uint32 timeoutEnd = Time::getMillisecondCounter() + (uint32) timeOutMilliseconds; + + while (isThreadRunning()) + { + if (timeOutMilliseconds >= 0 && Time::getMillisecondCounter() > timeoutEnd) + return false; + + sleep (2); + } + + return true; +} + +void Thread::stopThread (const int timeOutMilliseconds) +{ + // agh! You can't stop the thread that's calling this method! How on earth + // would that work?? + bassert (getCurrentThreadId() != getThreadId()); + + const ScopedLock sl (startStopLock); + + if (isThreadRunning()) + { + signalThreadShouldExit(); + notify(); + + if (timeOutMilliseconds != 0) + waitForThreadToExit (timeOutMilliseconds); + + if (isThreadRunning()) + { + // very bad karma if this point is reached, as there are bound to be + // locks and events left in silly states when a thread is killed by force.. + jassertfalse; + Logger::writeToLog ("!! killing thread by force !!"); + + killThread(); + + threadHandle = nullptr; + threadId = 0; + } + } +} + +//============================================================================== +bool Thread::setPriority (const int newPriority) +{ + // NB: deadlock possible if you try to set the thread prio from the thread itself, + // so using setCurrentThreadPriority instead in that case. + if (getCurrentThreadId() == getThreadId()) + return setCurrentThreadPriority (newPriority); + + const ScopedLock sl (startStopLock); + + if (setThreadPriority (threadHandle, newPriority)) + { + threadPriority = newPriority; + return true; + } + + return false; +} + +bool Thread::setCurrentThreadPriority (const int newPriority) +{ + return setThreadPriority (0, newPriority); +} + +void Thread::setAffinityMask (const uint32 newAffinityMask) +{ + affinityMask = newAffinityMask; +} + +//============================================================================== +bool Thread::wait (const int timeOutMilliseconds) const +{ + return defaultEvent.wait (timeOutMilliseconds); +} + +void Thread::notify() const +{ + defaultEvent.signal(); +} + +//============================================================================== +void SpinLock::enter() const noexcept +{ + if (! tryEnter()) + { + for (int i = 20; --i >= 0;) + if (tryEnter()) + return; + + while (! tryEnter()) + Thread::yield(); + } +} + +//============================================================================== +#if BEAST_UNIT_TESTS + +class AtomicTests : public UnitTest +{ +public: + AtomicTests() : UnitTest ("Atomics") {} + + void runTest() + { + beginTest ("Misc"); + + char a1[7]; + expect (numElementsInArray(a1) == 7); + int a2[3]; + expect (numElementsInArray(a2) == 3); + + expect (ByteOrder::swap ((uint16) 0x1122) == 0x2211); + expect (ByteOrder::swap ((uint32) 0x11223344) == 0x44332211); + expect (ByteOrder::swap ((uint64) literal64bit (0x1122334455667788)) == literal64bit (0x8877665544332211)); + + beginTest ("Atomic int"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic unsigned int"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic int32"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic uint32"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic long"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic void*"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic int*"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic float"); + AtomicTester ::testFloat (*this); + #if ! BEAST_64BIT_ATOMICS_UNAVAILABLE // 64-bit intrinsics aren't available on some old platforms + beginTest ("Atomic int64"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic uint64"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic double"); + AtomicTester ::testFloat (*this); + #endif + } + + template + class AtomicTester + { + public: + AtomicTester() {} + + static void testInteger (UnitTest& test) + { + Atomic a, b; + a.set ((Type) 10); + test.expect (a.value == (Type) 10); + test.expect (a.get() == (Type) 10); + a += (Type) 15; + test.expect (a.get() == (Type) 25); + a.memoryBarrier(); + a -= (Type) 5; + test.expect (a.get() == (Type) 20); + test.expect (++a == (Type) 21); + ++a; + test.expect (--a == (Type) 21); + test.expect (a.get() == (Type) 21); + a.memoryBarrier(); + + testFloat (test); + } + + static void testFloat (UnitTest& test) + { + Atomic a, b; + a = (Type) 21; + a.memoryBarrier(); + + /* These are some simple test cases to check the atomics - let me know + if any of these assertions fail on your system! + */ + test.expect (a.get() == (Type) 21); + test.expect (a.compareAndSetValue ((Type) 100, (Type) 50) == (Type) 21); + test.expect (a.get() == (Type) 21); + test.expect (a.compareAndSetValue ((Type) 101, a.get()) == (Type) 21); + test.expect (a.get() == (Type) 101); + test.expect (! a.compareAndSetBool ((Type) 300, (Type) 200)); + test.expect (a.get() == (Type) 101); + test.expect (a.compareAndSetBool ((Type) 200, a.get())); + test.expect (a.get() == (Type) 200); + + test.expect (a.exchange ((Type) 300) == (Type) 200); + test.expect (a.get() == (Type) 300); + + b = a; + test.expect (b.get() == a.get()); + } + }; +}; + +static AtomicTests atomicUnitTests; + +#endif diff --git a/modules/beast_core/threads/beast_Thread.h b/modules/beast_core/threads/beast_Thread.h new file mode 100644 index 0000000000..e553913da6 --- /dev/null +++ b/modules/beast_core/threads/beast_Thread.h @@ -0,0 +1,287 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_THREAD_BEASTHEADER +#define BEAST_THREAD_BEASTHEADER + +#include "beast_WaitableEvent.h" +#include "beast_CriticalSection.h" + + +//============================================================================== +/** + Encapsulates a thread. + + Subclasses derive from Thread and implement the run() method, in which they + do their business. The thread can then be started with the startThread() method + and controlled with various other methods. + + This class also contains some thread-related static methods, such + as sleep(), yield(), getCurrentThreadId() etc. + + @see CriticalSection, WaitableEvent, Process, ThreadWithProgressWindow, + MessageManagerLock +*/ +class BEAST_API Thread +{ +public: + //============================================================================== + /** + Creates a thread. + + When first created, the thread is not running. Use the startThread() + method to start it. + */ + explicit Thread (const String& threadName); + + /** Destructor. + + Deleting a Thread object that is running will only give the thread a + brief opportunity to stop itself cleanly, so it's recommended that you + should always call stopThread() with a decent timeout before deleting, + to avoid the thread being forcibly killed (which is a Bad Thing). + */ + virtual ~Thread(); + + //============================================================================== + /** Must be implemented to perform the thread's actual code. + + Remember that the thread must regularly check the threadShouldExit() + method whilst running, and if this returns true it should return from + the run() method as soon as possible to avoid being forcibly killed. + + @see threadShouldExit, startThread + */ + virtual void run() = 0; + + //============================================================================== + // Thread control functions.. + + /** Starts the thread running. + + This will start the thread's run() method. + (if it's already started, startThread() won't do anything). + + @see stopThread + */ + void startThread(); + + /** Starts the thread with a given priority. + + Launches the thread with a given priority, where 0 = lowest, 10 = highest. + If the thread is already running, its priority will be changed. + + @see startThread, setPriority + */ + void startThread (int priority); + + /** Attempts to stop the thread running. + + This method will cause the threadShouldExit() method to return true + and call notify() in case the thread is currently waiting. + + Hopefully the thread will then respond to this by exiting cleanly, and + the stopThread method will wait for a given time-period for this to + happen. + + If the thread is stuck and fails to respond after the time-out, it gets + forcibly killed, which is a very bad thing to happen, as it could still + be holding locks, etc. which are needed by other parts of your program. + + @param timeOutMilliseconds The number of milliseconds to wait for the + thread to finish before killing it by force. A negative + value in here will wait forever. + @see signalThreadShouldExit, threadShouldExit, waitForThreadToExit, isThreadRunning + */ + void stopThread (int timeOutMilliseconds); + + //============================================================================== + /** Returns true if the thread is currently active */ + bool isThreadRunning() const; + + /** Sets a flag to tell the thread it should stop. + + Calling this means that the threadShouldExit() method will then return true. + The thread should be regularly checking this to see whether it should exit. + + If your thread makes use of wait(), you might want to call notify() after calling + this method, to interrupt any waits that might be in progress, and allow it + to reach a point where it can exit. + + @see threadShouldExit + @see waitForThreadToExit + */ + void signalThreadShouldExit(); + + /** Checks whether the thread has been told to stop running. + + Threads need to check this regularly, and if it returns true, they should + return from their run() method at the first possible opportunity. + + @see signalThreadShouldExit + */ + inline bool threadShouldExit() const { return shouldExit; } + + /** Waits for the thread to stop. + + This will waits until isThreadRunning() is false or until a timeout expires. + + @param timeOutMilliseconds the time to wait, in milliseconds. If this value + is less than zero, it will wait forever. + @returns true if the thread exits, or false if the timeout expires first. + */ + bool waitForThreadToExit (int timeOutMilliseconds) const; + + //============================================================================== + /** Changes the thread's priority. + May return false if for some reason the priority can't be changed. + + @param priority the new priority, in the range 0 (lowest) to 10 (highest). A priority + of 5 is normal. + */ + bool setPriority (int priority); + + /** Changes the priority of the caller thread. + + Similar to setPriority(), but this static method acts on the caller thread. + May return false if for some reason the priority can't be changed. + + @see setPriority + */ + static bool setCurrentThreadPriority (int priority); + + //============================================================================== + /** Sets the affinity mask for the thread. + + This will only have an effect next time the thread is started - i.e. if the + thread is already running when called, it'll have no effect. + + @see setCurrentThreadAffinityMask + */ + void setAffinityMask (uint32 affinityMask); + + /** Changes the affinity mask for the caller thread. + + This will change the affinity mask for the thread that calls this static method. + + @see setAffinityMask + */ + static void setCurrentThreadAffinityMask (uint32 affinityMask); + + //============================================================================== + // this can be called from any thread that needs to pause.. + static void BEAST_CALLTYPE sleep (int milliseconds); + + /** Yields the calling thread's current time-slot. */ + static void BEAST_CALLTYPE yield(); + + //============================================================================== + /** Makes the thread wait for a notification. + + This puts the thread to sleep until either the timeout period expires, or + another thread calls the notify() method to wake it up. + + A negative time-out value means that the method will wait indefinitely. + + @returns true if the event has been signalled, false if the timeout expires. + */ + bool wait (int timeOutMilliseconds) const; + + /** Wakes up the thread. + + If the thread has called the wait() method, this will wake it up. + + @see wait + */ + void notify() const; + + //============================================================================== + /** A value type used for thread IDs. + @see getCurrentThreadId(), getThreadId() + */ + typedef void* ThreadID; + + /** Returns an id that identifies the caller thread. + + To find the ID of a particular thread object, use getThreadId(). + + @returns a unique identifier that identifies the calling thread. + @see getThreadId + */ + static ThreadID getCurrentThreadId(); + + /** Finds the thread object that is currently running. + + Note that the main UI thread (or other non-Beast threads) don't have a Thread + object associated with them, so this will return 0. + */ + static Thread* getCurrentThread(); + + /** Returns the ID of this thread. + + That means the ID of this thread object - not of the thread that's calling the method. + + This can change when the thread is started and stopped, and will be invalid if the + thread's not actually running. + + @see getCurrentThreadId + */ + ThreadID getThreadId() const noexcept { return threadId; } + + /** Returns the name of the thread. + + This is the name that gets set in the constructor. + */ + const String& getThreadName() const { return threadName; } + + /** Changes the name of the caller thread. + Different OSes may place different length or content limits on this name. + */ + static void setCurrentThreadName (const String& newThreadName); + + +private: + //============================================================================== + const String threadName; + void* volatile threadHandle; + ThreadID threadId; + CriticalSection startStopLock; + WaitableEvent startSuspensionEvent, defaultEvent; + int threadPriority; + uint32 affinityMask; + bool volatile shouldExit; + + #ifndef DOXYGEN + friend void BEAST_API beast_threadEntryPoint (void*); + #endif + + void launchThread(); + void closeThreadHandle(); + void killThread(); + void threadEntryPoint(); + static bool setThreadPriority (void*, int); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread) +}; + +#endif // BEAST_THREAD_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ThreadLocalValue.h b/modules/beast_core/threads/beast_ThreadLocalValue.h new file mode 100644 index 0000000000..2a0bd68fe4 --- /dev/null +++ b/modules/beast_core/threads/beast_ThreadLocalValue.h @@ -0,0 +1,194 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_THREADLOCALVALUE_BEASTHEADER +#define BEAST_THREADLOCALVALUE_BEASTHEADER + +// (NB: on win32, native thread-locals aren't possible in a dynamically loaded DLL in XP). +#if ! ((BEAST_MSVC && (BEAST_64BIT || ! defined (BeastPlugin_PluginCode))) \ + || (BEAST_MAC && BEAST_CLANG && defined (MAC_OS_X_VERSION_10_7) \ + && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) + #define BEAST_NO_COMPILER_THREAD_LOCAL 1 +#endif + +//============================================================================== +/** + Provides cross-platform support for thread-local objects. + + This class holds an internal list of objects of the templated type, keeping + an instance for each thread that requests one. The first time a thread attempts + to access its value, an object is created and added to the list for that thread. + + Typically, you'll probably want to create a static instance of a ThreadLocalValue + object, or hold one within a singleton. + + The templated class for your value could be a primitive type, or any class that + has a default constructor and copy operator. + + When a thread no longer needs to use its value, it can call releaseCurrentThreadStorage() + to allow the storage to be re-used by another thread. If a thread exits without calling + this method, the object storage will be left allocated until the ThreadLocalValue object + is deleted. +*/ +template +class ThreadLocalValue +{ +public: + /** */ + ThreadLocalValue() noexcept + { + } + + /** Destructor. + When this object is deleted, all the value objects for all threads will be deleted. + */ + ~ThreadLocalValue() + { + #if BEAST_NO_COMPILER_THREAD_LOCAL + for (ObjectHolder* o = first.value; o != nullptr;) + { + ObjectHolder* const next = o->next; + delete o; + o = next; + } + #endif + } + + /** Returns a reference to this thread's instance of the value. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + Type& operator*() const noexcept { return get(); } + + /** Returns a pointer to this thread's instance of the value. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + operator Type*() const noexcept { return &get(); } + + /** Accesses a method or field of the value object. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + Type* operator->() const noexcept { return &get(); } + + /** Assigns a new value to the thread-local object. */ + ThreadLocalValue& operator= (const Type& newValue) { get() = newValue; return *this; } + + /** Returns a reference to this thread's instance of the value. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + Type& get() const noexcept + { + #if BEAST_NO_COMPILER_THREAD_LOCAL + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + + for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) + if (o->threadId == threadId) + return o->object; + + for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) + { + if (o->threadId == nullptr) + { + { + SpinLock::ScopedLockType sl (lock); + + if (o->threadId != nullptr) + continue; + + o->threadId = threadId; + } + + o->object = Type(); + return o->object; + } + } + + ObjectHolder* const newObject = new ObjectHolder (threadId); + + do + { + newObject->next = first.get(); + } + while (! first.compareAndSetBool (newObject, newObject->next)); + + return newObject->object; + #elif BEAST_MAC + static __thread Type object; + return object; + #elif BEAST_MSVC + static __declspec(thread) Type object; + return object; + #endif + } + + /** Called by a thread before it terminates, to allow this class to release + any storage associated with the thread. + */ + void releaseCurrentThreadStorage() + { + #if BEAST_NO_COMPILER_THREAD_LOCAL + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + + for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) + { + if (o->threadId == threadId) + { + SpinLock::ScopedLockType sl (lock); + o->threadId = nullptr; + } + } + #endif + } + +private: + //============================================================================== + #if BEAST_NO_COMPILER_THREAD_LOCAL + struct ObjectHolder + { + ObjectHolder (const Thread::ThreadID& tid) + : threadId (tid), object() + {} + + Thread::ThreadID threadId; + ObjectHolder* next; + Type object; + + BEAST_DECLARE_NON_COPYABLE (ObjectHolder) + }; + + mutable Atomic first; + SpinLock lock; + #endif + + BEAST_DECLARE_NON_COPYABLE (ThreadLocalValue) +}; + + +#endif // BEAST_THREADLOCALVALUE_BEASTHEADER diff --git a/modules/beast_core/threads/beast_ThreadPool.cpp b/modules/beast_core/threads/beast_ThreadPool.cpp new file mode 100644 index 0000000000..a7247ef955 --- /dev/null +++ b/modules/beast_core/threads/beast_ThreadPool.cpp @@ -0,0 +1,375 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +ThreadPoolJob::ThreadPoolJob (const String& name) + : jobName (name), + pool (nullptr), + shouldStop (false), + isActive (false), + shouldBeDeleted (false) +{ +} + +ThreadPoolJob::~ThreadPoolJob() +{ + // you mustn't delete a job while it's still in a pool! Use ThreadPool::removeJob() + // to remove it first! + bassert (pool == nullptr || ! pool->contains (this)); +} + +String ThreadPoolJob::getJobName() const +{ + return jobName; +} + +void ThreadPoolJob::setJobName (const String& newName) +{ + jobName = newName; +} + +void ThreadPoolJob::signalJobShouldExit() +{ + shouldStop = true; +} + +//============================================================================== +class ThreadPool::ThreadPoolThread : public Thread +{ +public: + ThreadPoolThread (ThreadPool& pool_) + : Thread ("Pool"), + pool (pool_) + { + } + + void run() + { + while (! threadShouldExit()) + { + if (! pool.runNextJob()) + wait (500); + } + } + +private: + ThreadPool& pool; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) +}; + +//============================================================================== +ThreadPool::ThreadPool (const int numThreads) +{ + bassert (numThreads > 0); // not much point having a pool without any threads! + + createThreads (numThreads); +} + +ThreadPool::ThreadPool() +{ + createThreads (SystemStats::getNumCpus()); +} + +ThreadPool::~ThreadPool() +{ + removeAllJobs (true, 5000); + stopThreads(); +} + +void ThreadPool::createThreads (int numThreads) +{ + for (int i = bmax (1, numThreads); --i >= 0;) + threads.add (new ThreadPoolThread (*this)); + + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->startThread(); +} + +void ThreadPool::stopThreads() +{ + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->signalThreadShouldExit(); + + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->stopThread (500); +} + +void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinished) +{ + bassert (job != nullptr); + bassert (job->pool == nullptr); + + if (job->pool == nullptr) + { + job->pool = this; + job->shouldStop = false; + job->isActive = false; + job->shouldBeDeleted = deleteJobWhenFinished; + + { + const ScopedLock sl (lock); + jobs.add (job); + } + + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->notify(); + } +} + +int ThreadPool::getNumJobs() const +{ + return jobs.size(); +} + +ThreadPoolJob* ThreadPool::getJob (const int index) const +{ + const ScopedLock sl (lock); + return jobs [index]; +} + +bool ThreadPool::contains (const ThreadPoolJob* const job) const +{ + const ScopedLock sl (lock); + return jobs.contains (const_cast (job)); +} + +bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const +{ + const ScopedLock sl (lock); + return jobs.contains (const_cast (job)) && job->isActive; +} + +bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, + const int timeOutMs) const +{ + if (job != nullptr) + { + const uint32 start = Time::getMillisecondCounter(); + + while (contains (job)) + { + if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + (uint32) timeOutMs) + return false; + + jobFinishedSignal.wait (2); + } + } + + return true; +} + +bool ThreadPool::removeJob (ThreadPoolJob* const job, + const bool interruptIfRunning, + const int timeOutMs) +{ + bool dontWait = true; + OwnedArray deletionList; + + if (job != nullptr) + { + const ScopedLock sl (lock); + + if (jobs.contains (job)) + { + if (job->isActive) + { + if (interruptIfRunning) + job->signalJobShouldExit(); + + dontWait = false; + } + else + { + jobs.removeFirstMatchingValue (job); + addToDeleteList (deletionList, job); + } + } + } + + return dontWait || waitForJobToFinish (job, timeOutMs); +} + +bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, + ThreadPool::JobSelector* selectedJobsToRemove) +{ + Array jobsToWaitFor; + + { + OwnedArray deletionList; + + { + const ScopedLock sl (lock); + + for (int i = jobs.size(); --i >= 0;) + { + ThreadPoolJob* const job = jobs.getUnchecked(i); + + if (selectedJobsToRemove == nullptr || selectedJobsToRemove->isJobSuitable (job)) + { + if (job->isActive) + { + jobsToWaitFor.add (job); + + if (interruptRunningJobs) + job->signalJobShouldExit(); + } + else + { + jobs.remove (i); + addToDeleteList (deletionList, job); + } + } + } + } + } + + const uint32 start = Time::getMillisecondCounter(); + + for (;;) + { + for (int i = jobsToWaitFor.size(); --i >= 0;) + { + ThreadPoolJob* const job = jobsToWaitFor.getUnchecked (i); + + if (! isJobRunning (job)) + jobsToWaitFor.remove (i); + } + + if (jobsToWaitFor.size() == 0) + break; + + if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + (uint32) timeOutMs) + return false; + + jobFinishedSignal.wait (20); + } + + return true; +} + +StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) const +{ + StringArray s; + const ScopedLock sl (lock); + + for (int i = 0; i < jobs.size(); ++i) + { + const ThreadPoolJob* const job = jobs.getUnchecked(i); + if (job->isActive || ! onlyReturnActiveJobs) + s.add (job->getJobName()); + } + + return s; +} + +bool ThreadPool::setThreadPriorities (const int newPriority) +{ + bool ok = true; + + for (int i = threads.size(); --i >= 0;) + if (! threads.getUnchecked(i)->setPriority (newPriority)) + ok = false; + + return ok; +} + +ThreadPoolJob* ThreadPool::pickNextJobToRun() +{ + OwnedArray deletionList; + + { + const ScopedLock sl (lock); + + for (int i = 0; i < jobs.size(); ++i) + { + ThreadPoolJob* job = jobs[i]; + + if (job != nullptr && ! job->isActive) + { + if (job->shouldStop) + { + jobs.remove (i); + addToDeleteList (deletionList, job); + --i; + continue; + } + + job->isActive = true; + return job; + } + } + } + + return nullptr; +} + +bool ThreadPool::runNextJob() +{ + ThreadPoolJob* const job = pickNextJobToRun(); + + if (job == nullptr) + return false; + + ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; + + BEAST_TRY + { + result = job->runJob(); + } + BEAST_CATCH_ALL_ASSERT + + OwnedArray deletionList; + + { + const ScopedLock sl (lock); + + if (jobs.contains (job)) + { + job->isActive = false; + + if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) + { + jobs.removeFirstMatchingValue (job); + addToDeleteList (deletionList, job); + + jobFinishedSignal.signal(); + } + else + { + // move the job to the end of the queue if it wants another go + jobs.move (jobs.indexOf (job), -1); + } + } + } + + return true; +} + +void ThreadPool::addToDeleteList (OwnedArray& deletionList, ThreadPoolJob* const job) const +{ + job->shouldStop = true; + job->pool = nullptr; + + if (job->shouldBeDeleted) + deletionList.add (job); +} diff --git a/modules/beast_core/threads/beast_ThreadPool.h b/modules/beast_core/threads/beast_ThreadPool.h new file mode 100644 index 0000000000..d4d5dfe835 --- /dev/null +++ b/modules/beast_core/threads/beast_ThreadPool.h @@ -0,0 +1,313 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_THREADPOOL_BEASTHEADER +#define BEAST_THREADPOOL_BEASTHEADER + +#include "beast_Thread.h" +#include "../text/beast_StringArray.h" +#include "../containers/beast_Array.h" +#include "../containers/beast_OwnedArray.h" +class ThreadPool; +class ThreadPoolThread; + + +//============================================================================== +/** + A task that is executed by a ThreadPool object. + + A ThreadPool keeps a list of ThreadPoolJob objects which are executed by + its threads. + + The runJob() method needs to be implemented to do the task, and if the code that + does the work takes a significant time to run, it must keep checking the shouldExit() + method to see if something is trying to interrupt the job. If shouldExit() returns + true, the runJob() method must return immediately. + + @see ThreadPool, Thread +*/ +class BEAST_API ThreadPoolJob +{ +public: + //============================================================================== + /** Creates a thread pool job object. + After creating your job, add it to a thread pool with ThreadPool::addJob(). + */ + explicit ThreadPoolJob (const String& name); + + /** Destructor. */ + virtual ~ThreadPoolJob(); + + //============================================================================== + /** Returns the name of this job. + @see setJobName + */ + String getJobName() const; + + /** Changes the job's name. + @see getJobName + */ + void setJobName (const String& newName); + + //============================================================================== + /** These are the values that can be returned by the runJob() method. + */ + enum JobStatus + { + jobHasFinished = 0, /**< indicates that the job has finished and can be + removed from the pool. */ + + jobNeedsRunningAgain /**< indicates that the job would like to be called + again when a thread is free. */ + }; + + /** Peforms the actual work that this job needs to do. + + Your subclass must implement this method, in which is does its work. + + If the code in this method takes a significant time to run, it must repeatedly check + the shouldExit() method to see if something is trying to interrupt the job. + If shouldExit() ever returns true, the runJob() method must return immediately. + + If this method returns jobHasFinished, then the job will be removed from the pool + immediately. If it returns jobNeedsRunningAgain, then the job will be left in the + pool and will get a chance to run again as soon as a thread is free. + + @see shouldExit() + */ + virtual JobStatus runJob() = 0; + + + //============================================================================== + /** Returns true if this job is currently running its runJob() method. */ + bool isRunning() const noexcept { return isActive; } + + /** Returns true if something is trying to interrupt this job and make it stop. + + Your runJob() method must call this whenever it gets a chance, and if it ever + returns true, the runJob() method must return immediately. + + @see signalJobShouldExit() + */ + bool shouldExit() const noexcept { return shouldStop; } + + /** Calling this will cause the shouldExit() method to return true, and the job + should (if it's been implemented correctly) stop as soon as possible. + + @see shouldExit() + */ + void signalJobShouldExit(); + + //============================================================================== +private: + friend class ThreadPool; + friend class ThreadPoolThread; + String jobName; + ThreadPool* pool; + bool shouldStop, isActive, shouldBeDeleted; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolJob) +}; + + +//============================================================================== +/** + A set of threads that will run a list of jobs. + + When a ThreadPoolJob object is added to the ThreadPool's list, its runJob() method + will be called by the next pooled thread that becomes free. + + @see ThreadPoolJob, Thread +*/ +class BEAST_API ThreadPool +{ +public: + //============================================================================== + /** Creates a thread pool. + Once you've created a pool, you can give it some jobs by calling addJob(). + @param numberOfThreads the number of threads to run. These will be started + immediately, and will run until the pool is deleted. + */ + ThreadPool (int numberOfThreads); + + /** Creates a thread pool with one thread per CPU core. + Once you've created a pool, you can give it some jobs by calling addJob(). + If you want to specify the number of threads, use the other constructor; this + one creates a pool which has one thread for each CPU core. + @see SystemStats::getNumCpus() + */ + ThreadPool(); + + /** Destructor. + + This will attempt to remove all the jobs before deleting, but if you want to + specify a timeout, you should call removeAllJobs() explicitly before deleting + the pool. + */ + ~ThreadPool(); + + //============================================================================== + /** A callback class used when you need to select which ThreadPoolJob objects are suitable + for some kind of operation. + @see ThreadPool::removeAllJobs + */ + class BEAST_API JobSelector + { + public: + virtual ~JobSelector() {} + + /** Should return true if the specified thread matches your criteria for whatever + operation that this object is being used for. + + Any implementation of this method must be extremely fast and thread-safe! + */ + virtual bool isJobSuitable (ThreadPoolJob* job) = 0; + }; + + //============================================================================== + /** Adds a job to the queue. + + Once a job has been added, then the next time a thread is free, it will run + the job's ThreadPoolJob::runJob() method. Depending on the return value of the + runJob() method, the pool will either remove the job from the pool or add it to + the back of the queue to be run again. + + If deleteJobWhenFinished is true, then the job object will be owned and deleted by + the pool when not needed - if you do this, make sure that your object's destructor + is thread-safe. + + If deleteJobWhenFinished is false, the pointer will be used but not deleted, and + the caller is responsible for making sure the object is not deleted before it has + been removed from the pool. + */ + void addJob (ThreadPoolJob* job, + bool deleteJobWhenFinished); + + /** Tries to remove a job from the pool. + + If the job isn't yet running, this will simply remove it. If it is running, it + will wait for it to finish. + + If the timeout period expires before the job finishes running, then the job will be + left in the pool and this will return false. It returns true if the job is sucessfully + stopped and removed. + + @param job the job to remove + @param interruptIfRunning if true, then if the job is currently busy, its + ThreadPoolJob::signalJobShouldExit() method will be called to try + to interrupt it. If false, then if the job will be allowed to run + until it stops normally (or the timeout expires) + @param timeOutMilliseconds the length of time this method should wait for the job to finish + before giving up and returning false + */ + bool removeJob (ThreadPoolJob* job, + bool interruptIfRunning, + int timeOutMilliseconds); + + /** Tries to remove all jobs from the pool. + + @param interruptRunningJobs if true, then all running jobs will have their ThreadPoolJob::signalJobShouldExit() + methods called to try to interrupt them + @param timeOutMilliseconds the length of time this method should wait for all the jobs to finish + before giving up and returning false + @param selectedJobsToRemove if this is non-zero, the JobSelector object is asked to decide which + jobs should be removed. If it is zero, all jobs are removed + @returns true if all jobs are successfully stopped and removed; false if the timeout period + expires while waiting for one or more jobs to stop + */ + bool removeAllJobs (bool interruptRunningJobs, + int timeOutMilliseconds, + JobSelector* selectedJobsToRemove = nullptr); + + /** Returns the number of jobs currently running or queued. + */ + int getNumJobs() const; + + /** Returns one of the jobs in the queue. + + Note that this can be a very volatile list as jobs might be continuously getting shifted + around in the list, and this method may return 0 if the index is currently out-of-range. + */ + ThreadPoolJob* getJob (int index) const; + + /** Returns true if the given job is currently queued or running. + + @see isJobRunning() + */ + bool contains (const ThreadPoolJob* job) const; + + /** Returns true if the given job is currently being run by a thread. + */ + bool isJobRunning (const ThreadPoolJob* job) const; + + /** Waits until a job has finished running and has been removed from the pool. + + This will wait until the job is no longer in the pool - i.e. until its + runJob() method returns ThreadPoolJob::jobHasFinished. + + If the timeout period expires before the job finishes, this will return false; + it returns true if the job has finished successfully. + */ + bool waitForJobToFinish (const ThreadPoolJob* job, + int timeOutMilliseconds) const; + + /** Returns a list of the names of all the jobs currently running or queued. + If onlyReturnActiveJobs is true, only the ones currently running are returned. + */ + StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; + + /** Changes the priority of all the threads. + + This will call Thread::setPriority() for each thread in the pool. + May return false if for some reason the priority can't be changed. + */ + bool setThreadPriorities (int newPriority); + + +private: + //============================================================================== + Array jobs; + + class ThreadPoolThread; + friend class ThreadPoolThread; + friend class OwnedArray ; + OwnedArray threads; + + CriticalSection lock; + WaitableEvent jobFinishedSignal; + + bool runNextJob(); + ThreadPoolJob* pickNextJobToRun(); + void addToDeleteList (OwnedArray&, ThreadPoolJob*) const; + void createThreads (int numThreads); + void stopThreads(); + + // Note that this method has changed, and no longer has a parameter to indicate + // whether the jobs should be deleted - see the new method for details. + void removeAllJobs (bool, int, bool); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPool) +}; + + +#endif // BEAST_THREADPOOL_BEASTHEADER diff --git a/modules/beast_core/threads/beast_TimeSliceThread.cpp b/modules/beast_core/threads/beast_TimeSliceThread.cpp new file mode 100644 index 0000000000..106e7084db --- /dev/null +++ b/modules/beast_core/threads/beast_TimeSliceThread.cpp @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +TimeSliceThread::TimeSliceThread (const String& name) + : Thread (name), + clientBeingCalled (nullptr) +{ +} + +TimeSliceThread::~TimeSliceThread() +{ + stopThread (2000); +} + +//============================================================================== +void TimeSliceThread::addTimeSliceClient (TimeSliceClient* const client, int millisecondsBeforeStarting) +{ + if (client != nullptr) + { + const ScopedLock sl (listLock); + client->nextCallTime = Time::getCurrentTime() + RelativeTime::milliseconds (millisecondsBeforeStarting); + clients.addIfNotAlreadyThere (client); + notify(); + } +} + +void TimeSliceThread::removeTimeSliceClient (TimeSliceClient* const client) +{ + const ScopedLock sl1 (listLock); + + // if there's a chance we're in the middle of calling this client, we need to + // also lock the outer lock.. + if (clientBeingCalled == client) + { + const ScopedUnlock ul (listLock); // unlock first to get the order right.. + + const ScopedLock sl2 (callbackLock); + const ScopedLock sl3 (listLock); + + clients.removeFirstMatchingValue (client); + } + else + { + clients.removeFirstMatchingValue (client); + } +} + +void TimeSliceThread::moveToFrontOfQueue (TimeSliceClient* client) +{ + const ScopedLock sl (listLock); + + if (clients.contains (client)) + { + client->nextCallTime = Time::getCurrentTime(); + notify(); + } +} + +int TimeSliceThread::getNumClients() const +{ + return clients.size(); +} + +TimeSliceClient* TimeSliceThread::getClient (const int i) const +{ + const ScopedLock sl (listLock); + return clients [i]; +} + +//============================================================================== +TimeSliceClient* TimeSliceThread::getNextClient (int index) const +{ + Time soonest; + TimeSliceClient* client = nullptr; + + for (int i = clients.size(); --i >= 0;) + { + TimeSliceClient* const c = clients.getUnchecked ((i + index) % clients.size()); + + if (client == nullptr || c->nextCallTime < soonest) + { + client = c; + soonest = c->nextCallTime; + } + } + + return client; +} + +void TimeSliceThread::run() +{ + int index = 0; + + while (! threadShouldExit()) + { + int timeToWait = 500; + + { + Time nextClientTime; + + { + const ScopedLock sl2 (listLock); + + index = clients.size() > 0 ? ((index + 1) % clients.size()) : 0; + + if (TimeSliceClient* const firstClient = getNextClient (index)) + nextClientTime = firstClient->nextCallTime; + } + + const Time now (Time::getCurrentTime()); + + if (nextClientTime > now) + { + timeToWait = (int) bmin ((int64) 500, (nextClientTime - now).inMilliseconds()); + } + else + { + timeToWait = index == 0 ? 1 : 0; + + const ScopedLock sl (callbackLock); + + { + const ScopedLock sl2 (listLock); + clientBeingCalled = getNextClient (index); + } + + if (clientBeingCalled != nullptr) + { + const int msUntilNextCall = clientBeingCalled->useTimeSlice(); + + const ScopedLock sl2 (listLock); + + if (msUntilNextCall >= 0) + clientBeingCalled->nextCallTime = now + RelativeTime::milliseconds (msUntilNextCall); + else + clients.removeFirstMatchingValue (clientBeingCalled); + + clientBeingCalled = nullptr; + } + } + } + + if (timeToWait > 0) + wait (timeToWait); + } +} diff --git a/modules/beast_core/threads/beast_TimeSliceThread.h b/modules/beast_core/threads/beast_TimeSliceThread.h new file mode 100644 index 0000000000..eddba2e6e4 --- /dev/null +++ b/modules/beast_core/threads/beast_TimeSliceThread.h @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_TIMESLICETHREAD_BEASTHEADER +#define BEAST_TIMESLICETHREAD_BEASTHEADER + +#include "beast_Thread.h" +#include "../containers/beast_Array.h" +#include "../time/beast_Time.h" +class TimeSliceThread; + + +//============================================================================== +/** + Used by the TimeSliceThread class. + + To register your class with a TimeSliceThread, derive from this class and + use the TimeSliceThread::addTimeSliceClient() method to add it to the list. + + Make sure you always call TimeSliceThread::removeTimeSliceClient() before + deleting your client! + + @see TimeSliceThread +*/ +class BEAST_API TimeSliceClient +{ +public: + /** Destructor. */ + virtual ~TimeSliceClient() {} + + /** Called back by a TimeSliceThread. + + When you register this class with it, a TimeSliceThread will repeatedly call + this method. + + The implementation of this method should use its time-slice to do something that's + quick - never block for longer than absolutely necessary. + + @returns Your method should return the number of milliseconds which it would like to wait before being called + again. Returning 0 will make the thread call again as soon as possible (after possibly servicing + other busy clients). If you return a value below zero, your client will be removed from the list of clients, + and won't be called again. The value you specify isn't a guaranteee, and is only used as a hint by the + thread - the actual time before the next callback may be more or less than specified. + You can force the TimeSliceThread to wake up and poll again immediately by calling its notify() method. + */ + virtual int useTimeSlice() = 0; + + +private: + friend class TimeSliceThread; + Time nextCallTime; +}; + + +//============================================================================== +/** + A thread that keeps a list of clients, and calls each one in turn, giving them + all a chance to run some sort of short task. + + @see TimeSliceClient, Thread +*/ +class BEAST_API TimeSliceThread : public Thread +{ +public: + //============================================================================== + /** + Creates a TimeSliceThread. + + When first created, the thread is not running. Use the startThread() + method to start it. + */ + explicit TimeSliceThread (const String& threadName); + + /** Destructor. + + Deleting a Thread object that is running will only give the thread a + brief opportunity to stop itself cleanly, so it's recommended that you + should always call stopThread() with a decent timeout before deleting, + to avoid the thread being forcibly killed (which is a Bad Thing). + */ + ~TimeSliceThread(); + + //============================================================================== + /** Adds a client to the list. + + The client's callbacks will start after the number of milliseconds specified + by millisecondsBeforeStarting (and this may happen before this method has returned). + */ + void addTimeSliceClient (TimeSliceClient* client, int millisecondsBeforeStarting = 0); + + /** Removes a client from the list. + + This method will make sure that all callbacks to the client have completely + finished before the method returns. + */ + void removeTimeSliceClient (TimeSliceClient* client); + + /** If the given client is waiting in the queue, it will be moved to the front + and given a time-slice as soon as possible. + If the specified client has not been added, nothing will happen. + */ + void moveToFrontOfQueue (TimeSliceClient* client); + + /** Returns the number of registered clients. */ + int getNumClients() const; + + /** Returns one of the registered clients. */ + TimeSliceClient* getClient (int index) const; + + //============================================================================== + #ifndef DOXYGEN + void run(); + #endif + + //============================================================================== +private: + CriticalSection callbackLock, listLock; + Array clients; + TimeSliceClient* clientBeingCalled; + + TimeSliceClient* getNextClient (int index) const; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimeSliceThread) +}; + + +#endif // BEAST_TIMESLICETHREAD_BEASTHEADER diff --git a/modules/beast_core/threads/beast_WaitableEvent.h b/modules/beast_core/threads/beast_WaitableEvent.h new file mode 100644 index 0000000000..e95ef02044 --- /dev/null +++ b/modules/beast_core/threads/beast_WaitableEvent.h @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WAITABLEEVENT_BEASTHEADER +#define BEAST_WAITABLEEVENT_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** + Allows threads to wait for events triggered by other threads. + + A thread can call wait() on a WaitableObject, and this will suspend the + calling thread until another thread wakes it up by calling the signal() + method. +*/ +class BEAST_API WaitableEvent +{ +public: + //============================================================================== + /** Creates a WaitableEvent object. + + @param manualReset If this is false, the event will be reset automatically when the wait() + method is called. If manualReset is true, then once the event is signalled, + the only way to reset it will be by calling the reset() method. + */ + WaitableEvent (bool manualReset = false) noexcept; + + /** Destructor. + + If other threads are waiting on this object when it gets deleted, this + can cause nasty errors, so be careful! + */ + ~WaitableEvent() noexcept; + + //============================================================================== + /** Suspends the calling thread until the event has been signalled. + + This will wait until the object's signal() method is called by another thread, + or until the timeout expires. + + After the event has been signalled, this method will return true and if manualReset + was set to false in the WaitableEvent's constructor, then the event will be reset. + + @param timeOutMilliseconds the maximum time to wait, in milliseconds. A negative + value will cause it to wait forever. + + @returns true if the object has been signalled, false if the timeout expires first. + @see signal, reset + */ + bool wait (int timeOutMilliseconds = -1) const noexcept; + + //============================================================================== + /** Wakes up any threads that are currently waiting on this object. + + If signal() is called when nothing is waiting, the next thread to call wait() + will return immediately and reset the signal. + + If the WaitableEvent is manual reset, all current and future threads that wait upon this + object will be woken, until reset() is explicitly called. + + If the WaitableEvent is automatic reset, and one or more threads is waiting upon the object, + then one of them will be woken up. If no threads are currently waiting, then the next thread + to call wait() will be woken up. As soon as a thread is woken, the signal is automatically + reset. + + @see wait, reset + */ + void signal() const noexcept; + + //============================================================================== + /** Resets the event to an unsignalled state. + + If it's not already signalled, this does nothing. + */ + void reset() const noexcept; + + +private: + //============================================================================== + #if BEAST_WINDOWS + void* internal; + #else + mutable pthread_cond_t condition; + mutable pthread_mutex_t mutex; + mutable bool triggered, manualReset; + #endif + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaitableEvent) +}; + + +#endif // BEAST_WAITABLEEVENT_BEASTHEADER diff --git a/modules/beast_core/time/beast_PerformanceCounter.cpp b/modules/beast_core/time/beast_PerformanceCounter.cpp new file mode 100644 index 0000000000..fe1f82c8af --- /dev/null +++ b/modules/beast_core/time/beast_PerformanceCounter.cpp @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +PerformanceCounter::PerformanceCounter (const String& name_, + const int runsPerPrintout, + const File& loggingFile) + : name (name_), + numRuns (0), + runsPerPrint (runsPerPrintout), + totalTime (0), + outputFile (loggingFile) +{ + if (outputFile != File::nonexistent) + { + String s ("**** Counter for \""); + s << name_ << "\" started at: " + << Time::getCurrentTime().toString (true, true) + << newLine; + + outputFile.appendText (s, false, false); + } +} + +PerformanceCounter::~PerformanceCounter() +{ + printStatistics(); +} + +void PerformanceCounter::start() +{ + started = Time::getHighResolutionTicks(); +} + +void PerformanceCounter::stop() +{ + const int64 now = Time::getHighResolutionTicks(); + + totalTime += 1000.0 * Time::highResolutionTicksToSeconds (now - started); + + if (++numRuns == runsPerPrint) + printStatistics(); +} + +void PerformanceCounter::printStatistics() +{ + if (numRuns > 0) + { + String s ("Performance count for \""); + s << name << "\" - average over " << numRuns << " run(s) = "; + + const int micros = (int) (totalTime * (1000.0 / numRuns)); + + if (micros > 10000) + s << (micros/1000) << " millisecs"; + else + s << micros << " microsecs"; + + s << ", total = " << String (totalTime / 1000, 5) << " seconds"; + + Logger::outputDebugString (s); + + s << newLine; + + if (outputFile != File::nonexistent) + outputFile.appendText (s, false, false); + + numRuns = 0; + totalTime = 0; + } +} diff --git a/modules/beast_core/time/beast_PerformanceCounter.h b/modules/beast_core/time/beast_PerformanceCounter.h new file mode 100644 index 0000000000..f826511382 --- /dev/null +++ b/modules/beast_core/time/beast_PerformanceCounter.h @@ -0,0 +1,103 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_PERFORMANCECOUNTER_BEASTHEADER +#define BEAST_PERFORMANCECOUNTER_BEASTHEADER + +#include "../files/beast_File.h" + + +//============================================================================== +/** A timer for measuring performance of code and dumping the results to a file. + + e.g. @code + + PerformanceCounter pc ("fish", 50, "/temp/myfishlog.txt"); + + for (;;) + { + pc.start(); + + doSomethingFishy(); + + pc.stop(); + } + @endcode + + In this example, the time of each period between calling start/stop will be + measured and averaged over 50 runs, and the results printed to a file + every 50 times round the loop. +*/ +class BEAST_API PerformanceCounter +{ +public: + //============================================================================== + /** Creates a PerformanceCounter object. + + @param counterName the name used when printing out the statistics + @param runsPerPrintout the number of start/stop iterations before calling + printStatistics() + @param loggingFile a file to dump the results to - if this is File::nonexistent, + the results are just written to the debugger output + */ + PerformanceCounter (const String& counterName, + int runsPerPrintout = 100, + const File& loggingFile = File::nonexistent); + + /** Destructor. */ + ~PerformanceCounter(); + + //============================================================================== + /** Starts timing. + + @see stop + */ + void start(); + + /** Stops timing and prints out the results. + + The number of iterations before doing a printout of the + results is set in the constructor. + + @see start + */ + void stop(); + + /** Dumps the current metrics to the debugger output and to a file. + + As well as using Logger::outputDebugString to print the results, + this will write then to the file specified in the constructor (if + this was valid). + */ + void printStatistics(); + +private: + //============================================================================== + String name; + int numRuns, runsPerPrint; + double totalTime; + int64 started; + File outputFile; +}; + +#endif // BEAST_PERFORMANCECOUNTER_BEASTHEADER diff --git a/modules/beast_core/time/beast_RelativeTime.cpp b/modules/beast_core/time/beast_RelativeTime.cpp new file mode 100644 index 0000000000..85db5c9b9e --- /dev/null +++ b/modules/beast_core/time/beast_RelativeTime.cpp @@ -0,0 +1,133 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +RelativeTime::RelativeTime (const double secs) noexcept : seconds (secs) {} +RelativeTime::RelativeTime (const RelativeTime& other) noexcept : seconds (other.seconds) {} +RelativeTime::~RelativeTime() noexcept {} + +//============================================================================== +RelativeTime RelativeTime::milliseconds (const int milliseconds) noexcept { return RelativeTime (milliseconds * 0.001); } +RelativeTime RelativeTime::milliseconds (const int64 milliseconds) noexcept { return RelativeTime (milliseconds * 0.001); } +RelativeTime RelativeTime::minutes (const double numberOfMinutes) noexcept { return RelativeTime (numberOfMinutes * 60.0); } +RelativeTime RelativeTime::hours (const double numberOfHours) noexcept { return RelativeTime (numberOfHours * (60.0 * 60.0)); } +RelativeTime RelativeTime::days (const double numberOfDays) noexcept { return RelativeTime (numberOfDays * (60.0 * 60.0 * 24.0)); } +RelativeTime RelativeTime::weeks (const double numberOfWeeks) noexcept { return RelativeTime (numberOfWeeks * (60.0 * 60.0 * 24.0 * 7.0)); } + +//============================================================================== +int64 RelativeTime::inMilliseconds() const noexcept { return (int64) (seconds * 1000.0); } +double RelativeTime::inMinutes() const noexcept { return seconds / 60.0; } +double RelativeTime::inHours() const noexcept { return seconds / (60.0 * 60.0); } +double RelativeTime::inDays() const noexcept { return seconds / (60.0 * 60.0 * 24.0); } +double RelativeTime::inWeeks() const noexcept { return seconds / (60.0 * 60.0 * 24.0 * 7.0); } + +//============================================================================== +RelativeTime& RelativeTime::operator= (const RelativeTime& other) noexcept { seconds = other.seconds; return *this; } + +RelativeTime RelativeTime::operator+= (RelativeTime t) noexcept { seconds += t.seconds; return *this; } +RelativeTime RelativeTime::operator-= (RelativeTime t) noexcept { seconds -= t.seconds; return *this; } +RelativeTime RelativeTime::operator+= (const double secs) noexcept { seconds += secs; return *this; } +RelativeTime RelativeTime::operator-= (const double secs) noexcept { seconds -= secs; return *this; } + +RelativeTime operator+ (RelativeTime t1, RelativeTime t2) noexcept { return t1 += t2; } +RelativeTime operator- (RelativeTime t1, RelativeTime t2) noexcept { return t1 -= t2; } + +bool operator== (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() == t2.inSeconds(); } +bool operator!= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() != t2.inSeconds(); } +bool operator> (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() > t2.inSeconds(); } +bool operator< (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() < t2.inSeconds(); } +bool operator>= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() >= t2.inSeconds(); } +bool operator<= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() <= t2.inSeconds(); } + +//============================================================================== +static void translateTimeField (String& result, int n, const char* singular, const char* plural) +{ + result << TRANS (n == 1 ? singular : plural) + .replace (n == 1 ? "1" : "2", String (n)) + << ' '; +} + +String RelativeTime::getDescription (const String& returnValueForZeroTime) const +{ + if (seconds < 0.001 && seconds > -0.001) + return returnValueForZeroTime; + + String result; + result.preallocateBytes (32); + + if (seconds < 0) + result << '-'; + + int fieldsShown = 0; + int n = std::abs ((int) inWeeks()); + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 week"), NEEDS_TRANS("2 weeks")); + ++fieldsShown; + } + + n = std::abs ((int) inDays()) % 7; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 day"), NEEDS_TRANS("2 days")); + ++fieldsShown; + } + + if (fieldsShown < 2) + { + n = std::abs ((int) inHours()) % 24; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 hr"), NEEDS_TRANS("2 hrs")); + ++fieldsShown; + } + + if (fieldsShown < 2) + { + n = std::abs ((int) inMinutes()) % 60; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 min"), NEEDS_TRANS("2 mins")); + ++fieldsShown; + } + + if (fieldsShown < 2) + { + n = std::abs ((int) inSeconds()) % 60; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 sec"), NEEDS_TRANS("2 secs")); + ++fieldsShown; + } + + if (fieldsShown == 0) + { + n = std::abs ((int) inMilliseconds()) % 1000; + if (n > 0) + result << n << ' ' << TRANS ("ms"); + } + } + } + } + + return result.trimEnd(); +} diff --git a/modules/beast_core/time/beast_RelativeTime.h b/modules/beast_core/time/beast_RelativeTime.h new file mode 100644 index 0000000000..3a3d9e2eb7 --- /dev/null +++ b/modules/beast_core/time/beast_RelativeTime.h @@ -0,0 +1,176 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_RELATIVETIME_BEASTHEADER +#define BEAST_RELATIVETIME_BEASTHEADER + +#include "../text/beast_String.h" + + +//============================================================================== +/** A relative measure of time. + + The time is stored as a number of seconds, at double-precision floating + point accuracy, and may be positive or negative. + + If you need an absolute time, (i.e. a date + time), see the Time class. +*/ +class BEAST_API RelativeTime +{ +public: + //============================================================================== + /** Creates a RelativeTime. + + @param seconds the number of seconds, which may be +ve or -ve. + @see milliseconds, minutes, hours, days, weeks + */ + explicit RelativeTime (double seconds = 0.0) noexcept; + + /** Copies another relative time. */ + RelativeTime (const RelativeTime& other) noexcept; + + /** Copies another relative time. */ + RelativeTime& operator= (const RelativeTime& other) noexcept; + + /** Destructor. */ + ~RelativeTime() noexcept; + + //============================================================================== + /** Creates a new RelativeTime object representing a number of milliseconds. + @see minutes, hours, days, weeks + */ + static RelativeTime milliseconds (int milliseconds) noexcept; + + /** Creates a new RelativeTime object representing a number of milliseconds. + @see minutes, hours, days, weeks + */ + static RelativeTime milliseconds (int64 milliseconds) noexcept; + + /** Creates a new RelativeTime object representing a number of minutes. + @see milliseconds, hours, days, weeks + */ + static RelativeTime minutes (double numberOfMinutes) noexcept; + + /** Creates a new RelativeTime object representing a number of hours. + @see milliseconds, minutes, days, weeks + */ + static RelativeTime hours (double numberOfHours) noexcept; + + /** Creates a new RelativeTime object representing a number of days. + @see milliseconds, minutes, hours, weeks + */ + static RelativeTime days (double numberOfDays) noexcept; + + /** Creates a new RelativeTime object representing a number of weeks. + @see milliseconds, minutes, hours, days + */ + static RelativeTime weeks (double numberOfWeeks) noexcept; + + //============================================================================== + /** Returns the number of milliseconds this time represents. + @see milliseconds, inSeconds, inMinutes, inHours, inDays, inWeeks + */ + int64 inMilliseconds() const noexcept; + + /** Returns the number of seconds this time represents. + @see inMilliseconds, inMinutes, inHours, inDays, inWeeks + */ + double inSeconds() const noexcept { return seconds; } + + /** Returns the number of minutes this time represents. + @see inMilliseconds, inSeconds, inHours, inDays, inWeeks + */ + double inMinutes() const noexcept; + + /** Returns the number of hours this time represents. + @see inMilliseconds, inSeconds, inMinutes, inDays, inWeeks + */ + double inHours() const noexcept; + + /** Returns the number of days this time represents. + @see inMilliseconds, inSeconds, inMinutes, inHours, inWeeks + */ + double inDays() const noexcept; + + /** Returns the number of weeks this time represents. + @see inMilliseconds, inSeconds, inMinutes, inHours, inDays + */ + double inWeeks() const noexcept; + + /** Returns a readable textual description of the time. + + The exact format of the string returned will depend on + the magnitude of the time - e.g. + + "1 min 4 secs", "1 hr 45 mins", "2 weeks 5 days", "140 ms" + + so that only the two most significant units are printed. + + The returnValueForZeroTime value is the result that is returned if the + length is zero. Depending on your application you might want to use this + to return something more relevant like "empty" or "0 secs", etc. + + @see inMilliseconds, inSeconds, inMinutes, inHours, inDays, inWeeks + */ + String getDescription (const String& returnValueForZeroTime = "0") const; + + + //============================================================================== + /** Adds another RelativeTime to this one. */ + RelativeTime operator+= (RelativeTime timeToAdd) noexcept; + /** Subtracts another RelativeTime from this one. */ + RelativeTime operator-= (RelativeTime timeToSubtract) noexcept; + + /** Adds a number of seconds to this time. */ + RelativeTime operator+= (double secondsToAdd) noexcept; + /** Subtracts a number of seconds from this time. */ + RelativeTime operator-= (double secondsToSubtract) noexcept; + +private: + //============================================================================== + double seconds; +}; + +//============================================================================== +/** Compares two RelativeTimes. */ +bool operator== (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator!= (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator> (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator< (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator>= (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator<= (RelativeTime t1, RelativeTime t2) noexcept; + +//============================================================================== +/** Adds two RelativeTimes together. */ +RelativeTime operator+ (RelativeTime t1, RelativeTime t2) noexcept; +/** Subtracts two RelativeTimes. */ +RelativeTime operator- (RelativeTime t1, RelativeTime t2) noexcept; + + + +#endif // BEAST_RELATIVETIME_BEASTHEADER diff --git a/modules/beast_core/time/beast_Time.cpp b/modules/beast_core/time/beast_Time.cpp new file mode 100644 index 0000000000..c84c98eaf6 --- /dev/null +++ b/modules/beast_core/time/beast_Time.cpp @@ -0,0 +1,441 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +namespace TimeHelpers +{ + static struct tm millisToLocal (const int64 millis) noexcept + { + struct tm result; + const int64 seconds = millis / 1000; + + if (seconds < literal64bit (86400) || seconds >= literal64bit (2145916800)) + { + // use extended maths for dates beyond 1970 to 2037.. + const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); + const int64 jdm = seconds + timeZoneAdjustment + literal64bit (210866803200); + + const int days = (int) (jdm / literal64bit (86400)); + const int a = 32044 + days; + const int b = (4 * a + 3) / 146097; + const int c = a - (b * 146097) / 4; + const int d = (4 * c + 3) / 1461; + const int e = c - (d * 1461) / 4; + const int m = (5 * e + 2) / 153; + + result.tm_mday = e - (153 * m + 2) / 5 + 1; + result.tm_mon = m + 2 - 12 * (m / 10); + result.tm_year = b * 100 + d - 6700 + (m / 10); + result.tm_wday = (days + 1) % 7; + result.tm_yday = -1; + + int t = (int) (jdm % literal64bit (86400)); + result.tm_hour = t / 3600; + t %= 3600; + result.tm_min = t / 60; + result.tm_sec = t % 60; + result.tm_isdst = -1; + } + else + { + time_t now = static_cast (seconds); + + #if BEAST_WINDOWS + #ifdef _INC_TIME_INL + if (now >= 0 && now <= 0x793406fff) + localtime_s (&result, &now); + else + zerostruct (result); + #else + result = *localtime (&now); + #endif + #else + + localtime_r (&now, &result); // more thread-safe + #endif + } + + return result; + } + + static int extendedModulo (const int64 value, const int modulo) noexcept + { + return (int) (value >= 0 ? (value % modulo) + : (value - ((value / modulo) + 1) * modulo)); + } + + static inline String formatString (const String& format, const struct tm* const tm) + { + #if BEAST_ANDROID + typedef CharPointer_UTF8 StringType; + #elif BEAST_WINDOWS + typedef CharPointer_UTF16 StringType; + #else + typedef CharPointer_UTF32 StringType; + #endif + + for (size_t bufferSize = 256; ; bufferSize += 256) + { + HeapBlock buffer (bufferSize); + + #if BEAST_ANDROID + const size_t numChars = strftime (buffer, bufferSize - 1, format.toUTF8(), tm); + #elif BEAST_WINDOWS + const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toWideCharPointer(), tm); + #else + const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toUTF32(), tm); + #endif + + if (numChars > 0) + return String (StringType (buffer), + StringType (buffer) + (int) numChars); + } + } + + static uint32 lastMSCounterValue = 0; +} + +//============================================================================== +Time::Time() noexcept + : millisSinceEpoch (0) +{ +} + +Time::Time (const Time& other) noexcept + : millisSinceEpoch (other.millisSinceEpoch) +{ +} + +Time::Time (const int64 ms) noexcept + : millisSinceEpoch (ms) +{ +} + +Time::Time (const int year, + const int month, + const int day, + const int hours, + const int minutes, + const int seconds, + const int milliseconds, + const bool useLocalTime) noexcept +{ + bassert (year > 100); // year must be a 4-digit version + + if (year < 1971 || year >= 2038 || ! useLocalTime) + { + // use extended maths for dates beyond 1970 to 2037.. + const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) + : 0; + const int a = (13 - month) / 12; + const int y = year + 4800 - a; + const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 + + (y * 365) + (y / 4) - (y / 100) + (y / 400) + - 32045; + + const int64 s = ((int64) jd) * literal64bit (86400) - literal64bit (210866803200); + + millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment)) + + milliseconds; + } + else + { + struct tm t; + t.tm_year = year - 1900; + t.tm_mon = month; + t.tm_mday = day; + t.tm_hour = hours; + t.tm_min = minutes; + t.tm_sec = seconds; + t.tm_isdst = -1; + + millisSinceEpoch = 1000 * (int64) mktime (&t); + + if (millisSinceEpoch < 0) + millisSinceEpoch = 0; + else + millisSinceEpoch += milliseconds; + } +} + +Time::~Time() noexcept +{ +} + +Time& Time::operator= (const Time& other) noexcept +{ + millisSinceEpoch = other.millisSinceEpoch; + return *this; +} + +//============================================================================== +int64 Time::currentTimeMillis() noexcept +{ + #if BEAST_WINDOWS + struct _timeb t; + #ifdef _INC_TIME_INL + _ftime_s (&t); + #else + _ftime (&t); + #endif + return ((int64) t.time) * 1000 + t.millitm; + #else + struct timeval tv; + gettimeofday (&tv, nullptr); + return ((int64) tv.tv_sec) * 1000 + tv.tv_usec / 1000; + #endif +} + +Time BEAST_CALLTYPE Time::getCurrentTime() noexcept +{ + return Time (currentTimeMillis()); +} + +//============================================================================== +uint32 beast_millisecondsSinceStartup() noexcept; + +uint32 Time::getMillisecondCounter() noexcept +{ + const uint32 now = beast_millisecondsSinceStartup(); + + if (now < TimeHelpers::lastMSCounterValue) + { + // in multi-threaded apps this might be called concurrently, so + // make sure that our last counter value only increases and doesn't + // go backwards.. + if (now < TimeHelpers::lastMSCounterValue - 1000) + TimeHelpers::lastMSCounterValue = now; + } + else + { + TimeHelpers::lastMSCounterValue = now; + } + + return now; +} + +uint32 Time::getApproximateMillisecondCounter() noexcept +{ + if (TimeHelpers::lastMSCounterValue == 0) + getMillisecondCounter(); + + return TimeHelpers::lastMSCounterValue; +} + +void Time::waitForMillisecondCounter (const uint32 targetTime) noexcept +{ + for (;;) + { + const uint32 now = getMillisecondCounter(); + + if (now >= targetTime) + break; + + const int toWait = (int) (targetTime - now); + + if (toWait > 2) + { + Thread::sleep (bmin (20, toWait >> 1)); + } + else + { + // xxx should consider using mutex_pause on the mac as it apparently + // makes it seem less like a spinlock and avoids lowering the thread pri. + for (int i = 10; --i >= 0;) + Thread::yield(); + } + } +} + +//============================================================================== +double Time::highResolutionTicksToSeconds (const int64 ticks) noexcept +{ + return ticks / (double) getHighResolutionTicksPerSecond(); +} + +int64 Time::secondsToHighResolutionTicks (const double seconds) noexcept +{ + return (int64) (seconds * (double) getHighResolutionTicksPerSecond()); +} + +//============================================================================== +String Time::toString (const bool includeDate, + const bool includeTime, + const bool includeSeconds, + const bool use24HourClock) const noexcept +{ + String result; + + if (includeDate) + { + result << getDayOfMonth() << ' ' + << getMonthName (true) << ' ' + << getYear(); + + if (includeTime) + result << ' '; + } + + if (includeTime) + { + const int mins = getMinutes(); + + result << (use24HourClock ? getHours() : getHoursInAmPmFormat()) + << (mins < 10 ? ":0" : ":") << mins; + + if (includeSeconds) + { + const int secs = getSeconds(); + result << (secs < 10 ? ":0" : ":") << secs; + } + + if (! use24HourClock) + result << (isAfternoon() ? "pm" : "am"); + } + + return result.trimEnd(); +} + +String Time::formatted (const String& format) const +{ + struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); + return TimeHelpers::formatString (format, &t); +} + +//============================================================================== +int Time::getYear() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_year + 1900; } +int Time::getMonth() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mon; } +int Time::getDayOfYear() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_yday; } +int Time::getDayOfMonth() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mday; } +int Time::getDayOfWeek() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_wday; } +int Time::getHours() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_hour; } +int Time::getMinutes() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_min; } +int Time::getSeconds() const noexcept { return TimeHelpers::extendedModulo (millisSinceEpoch / 1000, 60); } +int Time::getMilliseconds() const noexcept { return TimeHelpers::extendedModulo (millisSinceEpoch, 1000); } + +int Time::getHoursInAmPmFormat() const noexcept +{ + const int hours = getHours(); + + if (hours == 0) return 12; + if (hours <= 12) return hours; + + return hours - 12; +} + +bool Time::isAfternoon() const noexcept +{ + return getHours() >= 12; +} + +bool Time::isDaylightSavingTime() const noexcept +{ + return TimeHelpers::millisToLocal (millisSinceEpoch).tm_isdst != 0; +} + +String Time::getTimeZone() const noexcept +{ + String zone[2]; + + #if BEAST_WINDOWS + _tzset(); + + #ifdef _INC_TIME_INL + for (int i = 0; i < 2; ++i) + { + char name[128] = { 0 }; + size_t length; + _get_tzname (&length, name, 127, i); + zone[i] = name; + } + #else + const char** const zonePtr = (const char**) _tzname; + zone[0] = zonePtr[0]; + zone[1] = zonePtr[1]; + #endif + #else + tzset(); + const char** const zonePtr = (const char**) tzname; + zone[0] = zonePtr[0]; + zone[1] = zonePtr[1]; + #endif + + if (isDaylightSavingTime()) + { + zone[0] = zone[1]; + + if (zone[0].length() > 3 + && zone[0].containsIgnoreCase ("daylight") + && zone[0].contains ("GMT")) + zone[0] = "BST"; + } + + return zone[0].substring (0, 3); +} + +String Time::getMonthName (const bool threeLetterVersion) const +{ + return getMonthName (getMonth(), threeLetterVersion); +} + +String Time::getWeekdayName (const bool threeLetterVersion) const +{ + return getWeekdayName (getDayOfWeek(), threeLetterVersion); +} + +String Time::getMonthName (int monthNumber, const bool threeLetterVersion) +{ + const char* const shortMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + const char* const longMonthNames[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + + monthNumber %= 12; + + return TRANS (threeLetterVersion ? shortMonthNames [monthNumber] + : longMonthNames [monthNumber]); +} + +String Time::getWeekdayName (int day, const bool threeLetterVersion) +{ + const char* const shortDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + const char* const longDayNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + + day %= 7; + + return TRANS (threeLetterVersion ? shortDayNames [day] + : longDayNames [day]); +} + +//============================================================================== +Time& Time::operator+= (RelativeTime delta) { millisSinceEpoch += delta.inMilliseconds(); return *this; } +Time& Time::operator-= (RelativeTime delta) { millisSinceEpoch -= delta.inMilliseconds(); return *this; } + +Time operator+ (Time time, RelativeTime delta) { Time t (time); return t += delta; } +Time operator- (Time time, RelativeTime delta) { Time t (time); return t -= delta; } +Time operator+ (RelativeTime delta, Time time) { Time t (time); return t += delta; } +const RelativeTime operator- (Time time1, Time time2) { return RelativeTime::milliseconds (time1.toMilliseconds() - time2.toMilliseconds()); } + +bool operator== (Time time1, Time time2) { return time1.toMilliseconds() == time2.toMilliseconds(); } +bool operator!= (Time time1, Time time2) { return time1.toMilliseconds() != time2.toMilliseconds(); } +bool operator< (Time time1, Time time2) { return time1.toMilliseconds() < time2.toMilliseconds(); } +bool operator> (Time time1, Time time2) { return time1.toMilliseconds() > time2.toMilliseconds(); } +bool operator<= (Time time1, Time time2) { return time1.toMilliseconds() <= time2.toMilliseconds(); } +bool operator>= (Time time1, Time time2) { return time1.toMilliseconds() >= time2.toMilliseconds(); } diff --git a/modules/beast_core/time/beast_Time.h b/modules/beast_core/time/beast_Time.h new file mode 100644 index 0000000000..a4717c47c9 --- /dev/null +++ b/modules/beast_core/time/beast_Time.h @@ -0,0 +1,401 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_TIME_BEASTHEADER +#define BEAST_TIME_BEASTHEADER + +#include "beast_RelativeTime.h" + + +//============================================================================== +/** + Holds an absolute date and time. + + Internally, the time is stored at millisecond precision. + + @see RelativeTime +*/ +class BEAST_API Time +{ +public: + //============================================================================== + /** Creates a Time object. + + This default constructor creates a time of 1st January 1970, (which is + represented internally as 0ms). + + To create a time object representing the current time, use getCurrentTime(). + + @see getCurrentTime + */ + Time() noexcept; + + /** Creates a time based on a number of milliseconds. + + The internal millisecond count is set to 0 (1st January 1970). To create a + time object set to the current time, use getCurrentTime(). + + @param millisecondsSinceEpoch the number of milliseconds since the unix + 'epoch' (midnight Jan 1st 1970). + @see getCurrentTime, currentTimeMillis + */ + explicit Time (int64 millisecondsSinceEpoch) noexcept; + + /** Creates a time from a set of date components. + + The timezone is assumed to be whatever the system is using as its locale. + + @param year the year, in 4-digit format, e.g. 2004 + @param month the month, in the range 0 to 11 + @param day the day of the month, in the range 1 to 31 + @param hours hours in 24-hour clock format, 0 to 23 + @param minutes minutes 0 to 59 + @param seconds seconds 0 to 59 + @param milliseconds milliseconds 0 to 999 + @param useLocalTime if true, encode using the current machine's local time; if + false, it will always work in GMT. + */ + Time (int year, + int month, + int day, + int hours, + int minutes, + int seconds = 0, + int milliseconds = 0, + bool useLocalTime = true) noexcept; + + /** Creates a copy of another Time object. */ + Time (const Time& other) noexcept; + + /** Destructor. */ + ~Time() noexcept; + + /** Copies this time from another one. */ + Time& operator= (const Time& other) noexcept; + + //============================================================================== + /** Returns a Time object that is set to the current system time. + + @see currentTimeMillis + */ + static Time BEAST_CALLTYPE getCurrentTime() noexcept; + + /** Returns the time as a number of milliseconds. + + @returns the number of milliseconds this Time object represents, since + midnight jan 1st 1970. + @see getMilliseconds + */ + int64 toMilliseconds() const noexcept { return millisSinceEpoch; } + + /** Returns the year. + + A 4-digit format is used, e.g. 2004. + */ + int getYear() const noexcept; + + /** Returns the number of the month. + + The value returned is in the range 0 to 11. + @see getMonthName + */ + int getMonth() const noexcept; + + /** Returns the name of the month. + + @param threeLetterVersion if true, it'll be a 3-letter abbreviation, e.g. "Jan"; if false + it'll return the long form, e.g. "January" + @see getMonth + */ + String getMonthName (bool threeLetterVersion) const; + + /** Returns the day of the month. + The value returned is in the range 1 to 31. + */ + int getDayOfMonth() const noexcept; + + /** Returns the number of the day of the week. + The value returned is in the range 0 to 6 (0 = sunday, 1 = monday, etc). + */ + int getDayOfWeek() const noexcept; + + /** Returns the number of the day of the year. + The value returned is in the range 0 to 365. + */ + int getDayOfYear() const noexcept; + + /** Returns the name of the weekday. + + @param threeLetterVersion if true, it'll return a 3-letter abbreviation, e.g. "Tue"; if + false, it'll return the full version, e.g. "Tuesday". + */ + String getWeekdayName (bool threeLetterVersion) const; + + /** Returns the number of hours since midnight. + + This is in 24-hour clock format, in the range 0 to 23. + + @see getHoursInAmPmFormat, isAfternoon + */ + int getHours() const noexcept; + + /** Returns true if the time is in the afternoon. + + So it returns true for "PM", false for "AM". + + @see getHoursInAmPmFormat, getHours + */ + bool isAfternoon() const noexcept; + + /** Returns the hours in 12-hour clock format. + + This will return a value 1 to 12 - use isAfternoon() to find out + whether this is in the afternoon or morning. + + @see getHours, isAfternoon + */ + int getHoursInAmPmFormat() const noexcept; + + /** Returns the number of minutes, 0 to 59. */ + int getMinutes() const noexcept; + + /** Returns the number of seconds, 0 to 59. */ + int getSeconds() const noexcept; + + /** Returns the number of milliseconds, 0 to 999. + + Unlike toMilliseconds(), this just returns the position within the + current second rather than the total number since the epoch. + + @see toMilliseconds + */ + int getMilliseconds() const noexcept; + + /** Returns true if the local timezone uses a daylight saving correction. */ + bool isDaylightSavingTime() const noexcept; + + /** Returns a 3-character string to indicate the local timezone. */ + String getTimeZone() const noexcept; + + //============================================================================== + /** Quick way of getting a string version of a date and time. + + For a more powerful way of formatting the date and time, see the formatted() method. + + @param includeDate whether to include the date in the string + @param includeTime whether to include the time in the string + @param includeSeconds if the time is being included, this provides an option not to include + the seconds in it + @param use24HourClock if the time is being included, sets whether to use am/pm or 24 + hour notation. + @see formatted + */ + String toString (bool includeDate, + bool includeTime, + bool includeSeconds = true, + bool use24HourClock = false) const noexcept; + + /** Converts this date/time to a string with a user-defined format. + + This uses the C strftime() function to format this time as a string. To save you + looking it up, these are the escape codes that strftime uses (other codes might + work on some platforms and not others, but these are the common ones): + + %a is replaced by the locale's abbreviated weekday name. + %A is replaced by the locale's full weekday name. + %b is replaced by the locale's abbreviated month name. + %B is replaced by the locale's full month name. + %c is replaced by the locale's appropriate date and time representation. + %d is replaced by the day of the month as a decimal number [01,31]. + %H is replaced by the hour (24-hour clock) as a decimal number [00,23]. + %I is replaced by the hour (12-hour clock) as a decimal number [01,12]. + %j is replaced by the day of the year as a decimal number [001,366]. + %m is replaced by the month as a decimal number [01,12]. + %M is replaced by the minute as a decimal number [00,59]. + %p is replaced by the locale's equivalent of either a.m. or p.m. + %S is replaced by the second as a decimal number [00,61]. + %U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + %w is replaced by the weekday as a decimal number [0,6], with 0 representing Sunday. + %W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. + %x is replaced by the locale's appropriate date representation. + %X is replaced by the locale's appropriate time representation. + %y is replaced by the year without century as a decimal number [00,99]. + %Y is replaced by the year with century as a decimal number. + %Z is replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists. + %% is replaced by %. + + @see toString + */ + String formatted (const String& format) const; + + //============================================================================== + /** Adds a RelativeTime to this time. */ + Time& operator+= (RelativeTime delta); + /** Subtracts a RelativeTime from this time. */ + Time& operator-= (RelativeTime delta); + + //============================================================================== + /** Tries to set the computer's clock. + + @returns true if this succeeds, although depending on the system, the + application might not have sufficient privileges to do this. + */ + bool setSystemTimeToThisTime() const; + + //============================================================================== + /** Returns the name of a day of the week. + + @param dayNumber the day, 0 to 6 (0 = sunday, 1 = monday, etc) + @param threeLetterVersion if true, it'll return a 3-letter abbreviation, e.g. "Tue"; if + false, it'll return the full version, e.g. "Tuesday". + */ + static String getWeekdayName (int dayNumber, + bool threeLetterVersion); + + /** Returns the name of one of the months. + + @param monthNumber the month, 0 to 11 + @param threeLetterVersion if true, it'll be a 3-letter abbreviation, e.g. "Jan"; if false + it'll return the long form, e.g. "January" + */ + static String getMonthName (int monthNumber, + bool threeLetterVersion); + + //============================================================================== + // Static methods for getting system timers directly.. + + /** Returns the current system time. + + Returns the number of milliseconds since midnight jan 1st 1970. + + Should be accurate to within a few millisecs, depending on platform, + hardware, etc. + */ + static int64 currentTimeMillis() noexcept; + + /** Returns the number of millisecs since a fixed event (usually system startup). + + This returns a monotonically increasing value which it unaffected by changes to the + system clock. It should be accurate to within a few millisecs, depending on platform, + hardware, etc. + + Being a 32-bit return value, it will of course wrap back to 0 after 2^32 seconds of + uptime, so be careful to take that into account. If you need a 64-bit time, you can + use currentTimeMillis() instead. + + @see getApproximateMillisecondCounter + */ + static uint32 getMillisecondCounter() noexcept; + + /** Returns the number of millisecs since a fixed event (usually system startup). + + This has the same function as getMillisecondCounter(), but returns a more accurate + value, using a higher-resolution timer if one is available. + + @see getMillisecondCounter + */ + static double getMillisecondCounterHiRes() noexcept; + + /** Waits until the getMillisecondCounter() reaches a given value. + + This will make the thread sleep as efficiently as it can while it's waiting. + */ + static void waitForMillisecondCounter (uint32 targetTime) noexcept; + + /** Less-accurate but faster version of getMillisecondCounter(). + + This will return the last value that getMillisecondCounter() returned, so doesn't + need to make a system call, but is less accurate - it shouldn't be more than + 100ms away from the correct time, though, so is still accurate enough for a + lot of purposes. + + @see getMillisecondCounter + */ + static uint32 getApproximateMillisecondCounter() noexcept; + + //============================================================================== + // High-resolution timers.. + + /** Returns the current high-resolution counter's tick-count. + + This is a similar idea to getMillisecondCounter(), but with a higher + resolution. + + @see getHighResolutionTicksPerSecond, highResolutionTicksToSeconds, + secondsToHighResolutionTicks + */ + static int64 getHighResolutionTicks() noexcept; + + /** Returns the resolution of the high-resolution counter in ticks per second. + + @see getHighResolutionTicks, highResolutionTicksToSeconds, + secondsToHighResolutionTicks + */ + static int64 getHighResolutionTicksPerSecond() noexcept; + + /** Converts a number of high-resolution ticks into seconds. + + @see getHighResolutionTicks, getHighResolutionTicksPerSecond, + secondsToHighResolutionTicks + */ + static double highResolutionTicksToSeconds (int64 ticks) noexcept; + + /** Converts a number seconds into high-resolution ticks. + + @see getHighResolutionTicks, getHighResolutionTicksPerSecond, + highResolutionTicksToSeconds + */ + static int64 secondsToHighResolutionTicks (double seconds) noexcept; + + +private: + //============================================================================== + int64 millisSinceEpoch; +}; + +//============================================================================== +/** Adds a RelativeTime to a Time. */ +BEAST_API Time operator+ (Time time, RelativeTime delta); +/** Adds a RelativeTime to a Time. */ +BEAST_API Time operator+ (RelativeTime delta, Time time); + +/** Subtracts a RelativeTime from a Time. */ +BEAST_API Time operator- (Time time, RelativeTime delta); +/** Returns the relative time difference between two times. */ +BEAST_API const RelativeTime operator- (Time time1, Time time2); + +/** Compares two Time objects. */ +BEAST_API bool operator== (Time time1, Time time2); +/** Compares two Time objects. */ +BEAST_API bool operator!= (Time time1, Time time2); +/** Compares two Time objects. */ +BEAST_API bool operator< (Time time1, Time time2); +/** Compares two Time objects. */ +BEAST_API bool operator<= (Time time1, Time time2); +/** Compares two Time objects. */ +BEAST_API bool operator> (Time time1, Time time2); +/** Compares two Time objects. */ +BEAST_API bool operator>= (Time time1, Time time2); + + +#endif // BEAST_TIME_BEASTHEADER diff --git a/modules/beast_core/unit_tests/beast_UnitTest.cpp b/modules/beast_core/unit_tests/beast_UnitTest.cpp new file mode 100644 index 0000000000..50d2abfa42 --- /dev/null +++ b/modules/beast_core/unit_tests/beast_UnitTest.cpp @@ -0,0 +1,232 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +UnitTest::UnitTest (const String& name_) + : name (name_), runner (nullptr) +{ + getAllTests().add (this); +} + +UnitTest::~UnitTest() +{ + getAllTests().removeFirstMatchingValue (this); +} + +Array& UnitTest::getAllTests() +{ + static Array tests; + return tests; +} + +void UnitTest::initialise() {} +void UnitTest::shutdown() {} + +void UnitTest::performTest (UnitTestRunner* const runner_) +{ + bassert (runner_ != nullptr); + runner = runner_; + + initialise(); + runTest(); + shutdown(); +} + +void UnitTest::logMessage (const String& message) +{ + runner->logMessage (message); +} + +void UnitTest::beginTest (const String& testName) +{ + runner->beginNewTest (this, testName); +} + +void UnitTest::expect (const bool result, const String& failureMessage) +{ + if (result) + runner->addPass(); + else + runner->addFail (failureMessage); +} + +//============================================================================== +UnitTestRunner::UnitTestRunner() + : currentTest (nullptr), + assertOnFailure (true), + logPasses (false) +{ +} + +UnitTestRunner::~UnitTestRunner() +{ +} + +void UnitTestRunner::setAssertOnFailure (bool shouldAssert) noexcept +{ + assertOnFailure = shouldAssert; +} + +void UnitTestRunner::setPassesAreLogged (bool shouldDisplayPasses) noexcept +{ + logPasses = shouldDisplayPasses; +} + +int UnitTestRunner::getNumResults() const noexcept +{ + return results.size(); +} + +const UnitTestRunner::TestResult* UnitTestRunner::getResult (int index) const noexcept +{ + return results [index]; +} + +void UnitTestRunner::resultsUpdated() +{ +} + +void UnitTestRunner::runTests (const Array& tests) +{ + results.clear(); + resultsUpdated(); + + for (int i = 0; i < tests.size(); ++i) + { + if (shouldAbortTests()) + break; + + try + { + tests.getUnchecked(i)->performTest (this); + } + catch (...) + { + addFail ("An unhandled exception was thrown!"); + } + } + + endTest(); +} + +void UnitTestRunner::runAllTests() +{ + runTests (UnitTest::getAllTests()); +} + +void UnitTestRunner::logMessage (const String& message) +{ + Logger::writeToLog (message); +} + +bool UnitTestRunner::shouldAbortTests() +{ + return false; +} + +void UnitTestRunner::beginNewTest (UnitTest* const test, const String& subCategory) +{ + endTest(); + currentTest = test; + + TestResult* const r = new TestResult(); + results.add (r); + r->unitTestName = test->getName(); + r->subcategoryName = subCategory; + r->passes = 0; + r->failures = 0; + + logMessage ("-----------------------------------------------------------------"); + logMessage ("Starting test: " + r->unitTestName + " / " + subCategory + "..."); + + resultsUpdated(); +} + +void UnitTestRunner::endTest() +{ + if (results.size() > 0) + { + TestResult* const r = results.getLast(); + + if (r->failures > 0) + { + String m ("FAILED!! "); + m << r->failures << (r->failures == 1 ? " test" : " tests") + << " failed, out of a total of " << (r->passes + r->failures); + + logMessage (String::empty); + logMessage (m); + logMessage (String::empty); + } + else + { + logMessage ("All tests completed successfully"); + } + } +} + +void UnitTestRunner::addPass() +{ + { + const ScopedLock sl (results.getLock()); + + TestResult* const r = results.getLast(); + bassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests! + + r->passes++; + + if (logPasses) + { + String message ("Test "); + message << (r->failures + r->passes) << " passed"; + logMessage (message); + } + } + + resultsUpdated(); +} + +void UnitTestRunner::addFail (const String& failureMessage) +{ + { + const ScopedLock sl (results.getLock()); + + TestResult* const r = results.getLast(); + bassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests! + + r->failures++; + + String message ("!!! Test "); + message << (r->failures + r->passes) << " failed"; + + if (failureMessage.isNotEmpty()) + message << ": " << failureMessage; + + r->messages.add (message); + + logMessage (message); + } + + resultsUpdated(); + + if (assertOnFailure) { jassertfalse; } +} diff --git a/modules/beast_core/unit_tests/beast_UnitTest.h b/modules/beast_core/unit_tests/beast_UnitTest.h new file mode 100644 index 0000000000..d155f0d7b5 --- /dev/null +++ b/modules/beast_core/unit_tests/beast_UnitTest.h @@ -0,0 +1,285 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_UNITTEST_BEASTHEADER +#define BEAST_UNITTEST_BEASTHEADER + +#include "../text/beast_StringArray.h" +#include "../containers/beast_OwnedArray.h" +class UnitTestRunner; + + +//============================================================================== +/** + This is a base class for classes that perform a unit test. + + To write a test using this class, your code should look something like this: + + @code + class MyTest : public UnitTest + { + public: + MyTest() : UnitTest ("Foobar testing") {} + + void runTest() + { + beginTest ("Part 1"); + + expect (myFoobar.doesSomething()); + expect (myFoobar.doesSomethingElse()); + + beginTest ("Part 2"); + + expect (myOtherFoobar.doesSomething()); + expect (myOtherFoobar.doesSomethingElse()); + + ...etc.. + } + }; + + // Creating a static instance will automatically add the instance to the array + // returned by UnitTest::getAllTests(), so the test will be included when you call + // UnitTestRunner::runAllTests() + static MyTest test; + @endcode + + To run a test, use the UnitTestRunner class. + + @see UnitTestRunner +*/ +class BEAST_API UnitTest +{ +public: + //============================================================================== + /** Creates a test with the given name. */ + explicit UnitTest (const String& name); + + /** Destructor. */ + virtual ~UnitTest(); + + /** Returns the name of the test. */ + const String& getName() const noexcept { return name; } + + /** Runs the test, using the specified UnitTestRunner. + You shouldn't need to call this method directly - use + UnitTestRunner::runTests() instead. + */ + void performTest (UnitTestRunner* runner); + + /** Returns the set of all UnitTest objects that currently exist. */ + static Array& getAllTests(); + + //============================================================================== + /** You can optionally implement this method to set up your test. + This method will be called before runTest(). + */ + virtual void initialise(); + + /** You can optionally implement this method to clear up after your test has been run. + This method will be called after runTest() has returned. + */ + virtual void shutdown(); + + /** Implement this method in your subclass to actually run your tests. + + The content of your implementation should call beginTest() and expect() + to perform the tests. + */ + virtual void runTest() = 0; + + //============================================================================== + /** Tells the system that a new subsection of tests is beginning. + This should be called from your runTest() method, and may be called + as many times as you like, to demarcate different sets of tests. + */ + void beginTest (const String& testName); + + //============================================================================== + /** Checks that the result of a test is true, and logs this result. + + In your runTest() method, you should call this method for each condition that + you want to check, e.g. + + @code + void runTest() + { + beginTest ("basic tests"); + expect (x + y == 2); + expect (getThing() == someThing); + ...etc... + } + @endcode + + If testResult is true, a pass is logged; if it's false, a failure is logged. + If the failure message is specified, it will be written to the log if the test fails. + */ + void expect (bool testResult, const String& failureMessage = String::empty); + + /** Compares two values, and if they don't match, prints out a message containing the + expected and actual result values. + */ + template + void expectEquals (ValueType actual, ValueType expected, String failureMessage = String::empty) + { + const bool result = (actual == expected); + + if (! result) + { + if (failureMessage.isNotEmpty()) + failureMessage << " -- "; + + failureMessage << "Expected value: " << expected << ", Actual value: " << actual; + } + + expect (result, failureMessage); + } + + //============================================================================== + /** Writes a message to the test log. + This can only be called from within your runTest() method. + */ + void logMessage (const String& message); + +private: + //============================================================================== + const String name; + UnitTestRunner* runner; + + BEAST_DECLARE_NON_COPYABLE (UnitTest) +}; + + +//============================================================================== +/** + Runs a set of unit tests. + + You can instantiate one of these objects and use it to invoke tests on a set of + UnitTest objects. + + By using a subclass of UnitTestRunner, you can intercept logging messages and + perform custom behaviour when each test completes. + + @see UnitTest +*/ +class BEAST_API UnitTestRunner +{ +public: + //============================================================================== + /** */ + UnitTestRunner(); + + /** Destructor. */ + virtual ~UnitTestRunner(); + + /** Runs a set of tests. + + The tests are performed in order, and the results are logged. To run all the + registered UnitTest objects that exist, use runAllTests(). + */ + void runTests (const Array& tests); + + /** Runs all the UnitTest objects that currently exist. + This calls runTests() for all the objects listed in UnitTest::getAllTests(). + */ + void runAllTests(); + + /** Sets a flag to indicate whether an assertion should be triggered if a test fails. + This is true by default. + */ + void setAssertOnFailure (bool shouldAssert) noexcept; + + /** Sets a flag to indicate whether successful tests should be logged. + By default, this is set to false, so that only failures will be displayed in the log. + */ + void setPassesAreLogged (bool shouldDisplayPasses) noexcept; + + //============================================================================== + /** Contains the results of a test. + + One of these objects is instantiated each time UnitTest::beginTest() is called, and + it contains details of the number of subsequent UnitTest::expect() calls that are + made. + */ + struct TestResult + { + /** The main name of this test (i.e. the name of the UnitTest object being run). */ + String unitTestName; + /** The name of the current subcategory (i.e. the name that was set when UnitTest::beginTest() was called). */ + String subcategoryName; + + /** The number of UnitTest::expect() calls that succeeded. */ + int passes; + /** The number of UnitTest::expect() calls that failed. */ + int failures; + + /** A list of messages describing the failed tests. */ + StringArray messages; + }; + + /** Returns the number of TestResult objects that have been performed. + @see getResult + */ + int getNumResults() const noexcept; + + /** Returns one of the TestResult objects that describes a test that has been run. + @see getNumResults + */ + const TestResult* getResult (int index) const noexcept; + +protected: + /** Called when the list of results changes. + You can override this to perform some sort of behaviour when results are added. + */ + virtual void resultsUpdated(); + + /** Logs a message about the current test progress. + By default this just writes the message to the Logger class, but you could override + this to do something else with the data. + */ + virtual void logMessage (const String& message); + + /** This can be overridden to let the runner know that it should abort the tests + as soon as possible, e.g. because the thread needs to stop. + */ + virtual bool shouldAbortTests(); + +private: + //============================================================================== + friend class UnitTest; + + UnitTest* currentTest; + String currentSubCategory; + OwnedArray results; + bool assertOnFailure, logPasses; + + void beginNewTest (UnitTest* test, const String& subCategory); + void endTest(); + + void addPass(); + void addFail (const String& failureMessage); + + BEAST_DECLARE_NON_COPYABLE (UnitTestRunner) +}; + + +#endif // BEAST_UNITTEST_BEASTHEADER diff --git a/modules/beast_core/xml/beast_XmlDocument.cpp b/modules/beast_core/xml/beast_XmlDocument.cpp new file mode 100644 index 0000000000..657a47cbc8 --- /dev/null +++ b/modules/beast_core/xml/beast_XmlDocument.cpp @@ -0,0 +1,848 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +XmlDocument::XmlDocument (const String& documentText) + : originalText (documentText), + input (nullptr), + ignoreEmptyTextElements (true) +{ +} + +XmlDocument::XmlDocument (const File& file) + : input (nullptr), + ignoreEmptyTextElements (true), + inputSource (new FileInputSource (file)) +{ +} + +XmlDocument::~XmlDocument() +{ +} + +XmlElement* XmlDocument::parse (const File& file) +{ + XmlDocument doc (file); + return doc.getDocumentElement(); +} + +XmlElement* XmlDocument::parse (const String& xmlData) +{ + XmlDocument doc (xmlData); + return doc.getDocumentElement(); +} + +void XmlDocument::setInputSource (InputSource* const newSource) noexcept +{ + inputSource = newSource; +} + +void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept +{ + ignoreEmptyTextElements = shouldBeIgnored; +} + +namespace XmlIdentifierChars +{ + static bool isIdentifierCharSlow (const beast_wchar c) noexcept + { + return CharacterFunctions::isLetterOrDigit (c) + || c == '_' || c == '-' || c == ':' || c == '.'; + } + + static bool isIdentifierChar (const beast_wchar c) noexcept + { + static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 }; + + return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0) + : isIdentifierCharSlow (c); + } + + /*static void generateIdentifierCharConstants() + { + uint32 n[8] = { 0 }; + for (int i = 0; i < 256; ++i) + if (isIdentifierCharSlow (i)) + n[i >> 5] |= (1 << (i & 31)); + + String s; + for (int i = 0; i < 8; ++i) + s << "0x" << String::toHexString ((int) n[i]) << ", "; + + DBG (s); + }*/ +} + +XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) +{ + String textToParse (originalText); + + if (textToParse.isEmpty() && inputSource != nullptr) + { + ScopedPointer in (inputSource->createInputStream()); + + if (in != nullptr) + { + MemoryOutputStream data; + data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); + textToParse = data.toString(); + + if (! onlyReadOuterDocumentElement) + originalText = textToParse; + } + } + + input = textToParse.getCharPointer(); + lastError = String::empty; + errorOccurred = false; + outOfData = false; + needToLoadDTD = true; + + if (textToParse.isEmpty()) + { + lastError = "not enough input"; + } + else + { + skipHeader(); + + if (input.getAddress() != nullptr) + { + ScopedPointer result (readNextElement (! onlyReadOuterDocumentElement)); + + if (! errorOccurred) + return result.release(); + } + else + { + lastError = "incorrect xml header"; + } + } + + return nullptr; +} + +const String& XmlDocument::getLastParseError() const noexcept +{ + return lastError; +} + +void XmlDocument::setLastError (const String& desc, const bool carryOn) +{ + lastError = desc; + errorOccurred = ! carryOn; +} + +String XmlDocument::getFileContents (const String& filename) const +{ + if (inputSource != nullptr) + { + const ScopedPointer in (inputSource->createInputStreamFor (filename.trim().unquoted())); + + if (in != nullptr) + return in->readEntireStreamAsString(); + } + + return String::empty; +} + +beast_wchar XmlDocument::readNextChar() noexcept +{ + const beast_wchar c = input.getAndAdvance(); + + if (c == 0) + { + outOfData = true; + --input; + } + + return c; +} + +int XmlDocument::findNextTokenLength() noexcept +{ + int len = 0; + beast_wchar c = *input; + + while (XmlIdentifierChars::isIdentifierChar (c)) + c = input [++len]; + + return len; +} + +void XmlDocument::skipHeader() +{ + const int headerStart = input.indexOf (CharPointer_UTF8 ("= 0) + { + const int headerEnd = (input + headerStart).indexOf (CharPointer_UTF8 ("?>")); + if (headerEnd < 0) + return; + + #if BEAST_DEBUG + const String header (input + headerStart, (size_t) (headerEnd - headerStart)); + const String encoding (header.fromFirstOccurrenceOf ("encoding", false, true) + .fromFirstOccurrenceOf ("=", false, false) + .fromFirstOccurrenceOf ("\"", false, false) + .upToFirstOccurrenceOf ("\"", false, false).trim()); + + /* If you load an XML document with a non-UTF encoding type, it may have been + loaded wrongly.. Since all the files are read via the normal beast file streams, + they're treated as UTF-8, so by the time it gets to the parser, the encoding will + have been lost. Best plan is to stick to utf-8 or if you have specific files to + read, use your own code to convert them to a unicode String, and pass that to the + XML parser. + */ + bassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-")); + #endif + + input += headerEnd + 2; + } + + skipNextWhiteSpace(); + + const int docTypeIndex = input.indexOf (CharPointer_UTF8 (" 0) + { + const beast_wchar c = readNextChar(); + + if (outOfData) + return; + + if (c == '<') + ++n; + else if (c == '>') + --n; + } + + dtdText = String (docType, (size_t) (input.getAddress() - (docType.getAddress() + 1))).trim(); +} + +void XmlDocument::skipNextWhiteSpace() +{ + for (;;) + { + beast_wchar c = *input; + + while (CharacterFunctions::isWhitespace (c)) + c = *++input; + + if (c == 0) + { + outOfData = true; + break; + } + else if (c == '<') + { + if (input[1] == '!' + && input[2] == '-' + && input[3] == '-') + { + input += 4; + const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); + + if (closeComment < 0) + { + outOfData = true; + break; + } + + input += closeComment + 3; + continue; + } + else if (input[1] == '?') + { + input += 2; + const int closeBracket = input.indexOf (CharPointer_UTF8 ("?>")); + + if (closeBracket < 0) + { + outOfData = true; + break; + } + + input += closeBracket + 2; + continue; + } + } + + break; + } +} + +void XmlDocument::readQuotedString (String& result) +{ + const beast_wchar quote = readNextChar(); + + while (! outOfData) + { + const beast_wchar c = readNextChar(); + + if (c == quote) + break; + + --input; + + if (c == '&') + { + readEntity (result); + } + else + { + const String::CharPointerType start (input); + size_t numChars = 0; + + for (;;) + { + const beast_wchar character = *input; + + if (character == quote) + { + result.appendCharPointer (start, numChars); + ++input; + return; + } + else if (character == '&') + { + result.appendCharPointer (start, numChars); + break; + } + else if (character == 0) + { + outOfData = true; + setLastError ("unmatched quotes", false); + break; + } + + ++input; + ++numChars; + } + } + } +} + +XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) +{ + XmlElement* node = nullptr; + + skipNextWhiteSpace(); + if (outOfData) + return nullptr; + + const int openBracket = input.indexOf ((beast_wchar) '<'); + + if (openBracket >= 0) + { + input += openBracket + 1; + int tagLen = findNextTokenLength(); + + if (tagLen == 0) + { + // no tag name - but allow for a gap after the '<' before giving an error + skipNextWhiteSpace(); + tagLen = findNextTokenLength(); + + if (tagLen == 0) + { + setLastError ("tag name missing", false); + return node; + } + } + + node = new XmlElement (String (input, (size_t) tagLen)); + input += tagLen; + LinkedListPointer::Appender attributeAppender (node->attributes); + + // look for attributes + for (;;) + { + skipNextWhiteSpace(); + + const beast_wchar c = *input; + + // empty tag.. + if (c == '/' && input[1] == '>') + { + input += 2; + break; + } + + // parse the guts of the element.. + if (c == '>') + { + ++input; + + if (alsoParseSubElements) + readChildElements (node); + + break; + } + + // get an attribute.. + if (XmlIdentifierChars::isIdentifierChar (c)) + { + const int attNameLen = findNextTokenLength(); + + if (attNameLen > 0) + { + const String::CharPointerType attNameStart (input); + input += attNameLen; + + skipNextWhiteSpace(); + + if (readNextChar() == '=') + { + skipNextWhiteSpace(); + + const beast_wchar nextChar = *input; + + if (nextChar == '"' || nextChar == '\'') + { + XmlElement::XmlAttributeNode* const newAtt + = new XmlElement::XmlAttributeNode (String (attNameStart, (size_t) attNameLen), + String::empty); + + readQuotedString (newAtt->value); + attributeAppender.append (newAtt); + continue; + } + } + } + } + else + { + if (! outOfData) + setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); + } + + break; + } + } + + return node; +} + +void XmlDocument::readChildElements (XmlElement* parent) +{ + LinkedListPointer::Appender childAppender (parent->firstChildElement); + + for (;;) + { + const String::CharPointerType preWhitespaceInput (input); + skipNextWhiteSpace(); + + if (outOfData) + { + setLastError ("unmatched tags", false); + break; + } + + if (*input == '<') + { + if (input[1] == '/') + { + // our close tag.. + const int closeTag = input.indexOf ((beast_wchar) '>'); + + if (closeTag >= 0) + input += closeTag + 1; + + break; + } + else if (input[1] == '!' + && input[2] == '[' + && input[3] == 'C' + && input[4] == 'D' + && input[5] == 'A' + && input[6] == 'T' + && input[7] == 'A' + && input[8] == '[') + { + input += 9; + const String::CharPointerType inputStart (input); + + size_t len = 0; + + for (;;) + { + if (*input == 0) + { + setLastError ("unterminated CDATA section", false); + outOfData = true; + break; + } + else if (input[0] == ']' + && input[1] == ']' + && input[2] == '>') + { + input += 3; + break; + } + + ++input; + ++len; + } + + childAppender.append (XmlElement::createTextElement (String (inputStart, len))); + } + else + { + // this is some other element, so parse and add it.. + if (XmlElement* const n = readNextElement (true)) + childAppender.append (n); + else + break; + } + } + else // must be a character block + { + input = preWhitespaceInput; // roll back to include the leading whitespace + String textElementContent; + + for (;;) + { + const beast_wchar c = *input; + + if (c == '<') + break; + + if (c == 0) + { + setLastError ("unmatched tags", false); + outOfData = true; + return; + } + + if (c == '&') + { + String entity; + readEntity (entity); + + if (entity.startsWithChar ('<') && entity [1] != 0) + { + const String::CharPointerType oldInput (input); + const bool oldOutOfData = outOfData; + + input = entity.getCharPointer(); + outOfData = false; + + for (;;) + { + XmlElement* const n = readNextElement (true); + + if (n == nullptr) + break; + + childAppender.append (n); + } + + input = oldInput; + outOfData = oldOutOfData; + } + else + { + textElementContent += entity; + } + } + else + { + const String::CharPointerType start (input); + size_t len = 0; + + for (;;) + { + const beast_wchar nextChar = *input; + + if (nextChar == '<' || nextChar == '&') + { + break; + } + else if (nextChar == 0) + { + setLastError ("unmatched tags", false); + outOfData = true; + return; + } + + ++input; + ++len; + } + + textElementContent.appendCharPointer (start, len); + } + } + + if ((! ignoreEmptyTextElements) || textElementContent.containsNonWhitespaceChars()) + { + childAppender.append (XmlElement::createTextElement (textElementContent)); + } + } + } +} + +void XmlDocument::readEntity (String& result) +{ + // skip over the ampersand + ++input; + + if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("amp;"), 4) == 0) + { + input += 4; + result += '&'; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("quot;"), 5) == 0) + { + input += 5; + result += '"'; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("apos;"), 5) == 0) + { + input += 5; + result += '\''; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("lt;"), 3) == 0) + { + input += 3; + result += '<'; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("gt;"), 3) == 0) + { + input += 3; + result += '>'; + } + else if (*input == '#') + { + int charCode = 0; + ++input; + + if (*input == 'x' || *input == 'X') + { + ++input; + int numChars = 0; + + while (input[0] != ';') + { + const int hexValue = CharacterFunctions::getHexDigitValue (input[0]); + + if (hexValue < 0 || ++numChars > 8) + { + setLastError ("illegal escape sequence", true); + break; + } + + charCode = (charCode << 4) | hexValue; + ++input; + } + + ++input; + } + else if (input[0] >= '0' && input[0] <= '9') + { + int numChars = 0; + + while (input[0] != ';') + { + if (++numChars > 12) + { + setLastError ("illegal escape sequence", true); + break; + } + + charCode = charCode * 10 + ((int) input[0] - '0'); + ++input; + } + + ++input; + } + else + { + setLastError ("illegal escape sequence", true); + result += '&'; + return; + } + + result << (beast_wchar) charCode; + } + else + { + const String::CharPointerType entityNameStart (input); + const int closingSemiColon = input.indexOf ((beast_wchar) ';'); + + if (closingSemiColon < 0) + { + outOfData = true; + result += '&'; + } + else + { + input += closingSemiColon + 1; + + result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon)); + } + } +} + +String XmlDocument::expandEntity (const String& ent) +{ + if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&'); + if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"'); + if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\''); + if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<'); + if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>'); + + if (ent[0] == '#') + { + const beast_wchar char1 = ent[1]; + + if (char1 == 'x' || char1 == 'X') + return String::charToString (static_cast (ent.substring (2).getHexValue32())); + + if (char1 >= '0' && char1 <= '9') + return String::charToString (static_cast (ent.substring (1).getIntValue())); + + setLastError ("illegal escape sequence", false); + return String::charToString ('&'); + } + + return expandExternalEntity (ent); +} + +String XmlDocument::expandExternalEntity (const String& entity) +{ + if (needToLoadDTD) + { + if (dtdText.isNotEmpty()) + { + dtdText = dtdText.trimCharactersAtEnd (">"); + tokenisedDTD.addTokens (dtdText, true); + + if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") + && tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) + { + const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); + + tokenisedDTD.clear(); + tokenisedDTD.addTokens (getFileContents (fn), true); + } + else + { + tokenisedDTD.clear(); + const int openBracket = dtdText.indexOfChar ('['); + + if (openBracket > 0) + { + const int closeBracket = dtdText.lastIndexOfChar (']'); + + if (closeBracket > openBracket) + tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, + closeBracket), true); + } + } + + for (int i = tokenisedDTD.size(); --i >= 0;) + { + if (tokenisedDTD[i].startsWithChar ('%') + && tokenisedDTD[i].endsWithChar (';')) + { + const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); + StringArray newToks; + newToks.addTokens (parsed, true); + + tokenisedDTD.remove (i); + + for (int j = newToks.size(); --j >= 0;) + tokenisedDTD.insert (i, newToks[j]); + } + } + } + + needToLoadDTD = false; + } + + for (int i = 0; i < tokenisedDTD.size(); ++i) + { + if (tokenisedDTD[i] == entity) + { + if (tokenisedDTD[i - 1].equalsIgnoreCase ("").trim().unquoted()); + + // check for sub-entities.. + int ampersand = ent.indexOfChar ('&'); + + while (ampersand >= 0) + { + const int semiColon = ent.indexOf (i + 1, ";"); + + if (semiColon < 0) + { + setLastError ("entity without terminating semi-colon", false); + break; + } + + const String resolved (expandEntity (ent.substring (i + 1, semiColon))); + + ent = ent.substring (0, ampersand) + + resolved + + ent.substring (semiColon + 1); + + ampersand = ent.indexOfChar (semiColon + 1, '&'); + } + + return ent; + } + } + } + + setLastError ("unknown entity", true); + + return entity; +} + +String XmlDocument::getParameterEntity (const String& entity) +{ + for (int i = 0; i < tokenisedDTD.size(); ++i) + { + if (tokenisedDTD[i] == entity + && tokenisedDTD [i - 1] == "%" + && tokenisedDTD [i - 2].equalsIgnoreCase ("")); + + if (ent.equalsIgnoreCase ("system")) + return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); + + return ent.trim().unquoted(); + } + } + + return entity; +} diff --git a/modules/beast_core/xml/beast_XmlDocument.h b/modules/beast_core/xml/beast_XmlDocument.h new file mode 100644 index 0000000000..443902225e --- /dev/null +++ b/modules/beast_core/xml/beast_XmlDocument.h @@ -0,0 +1,181 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_XMLDOCUMENT_BEASTHEADER +#define BEAST_XMLDOCUMENT_BEASTHEADER + +#include "beast_XmlElement.h" +#include "../text/beast_StringArray.h" +#include "../files/beast_File.h" +#include "../memory/beast_ScopedPointer.h" +class InputSource; + + +//============================================================================== +/** + Parses a text-based XML document and creates an XmlElement object from it. + + The parser will parse DTDs to load external entities but won't + check the document for validity against the DTD. + + e.g. + @code + + XmlDocument myDocument (File ("myfile.xml")); + XmlElement* mainElement = myDocument.getDocumentElement(); + + if (mainElement == nullptr) + { + String error = myDocument.getLastParseError(); + } + else + { + ..use the element + } + + @endcode + + Or you can use the static helper methods for quick parsing.. + + @code + XmlElement* xml = XmlDocument::parse (myXmlFile); + + if (xml != nullptr && xml->hasTagName ("foobar")) + { + ...etc + @endcode + + @see XmlElement +*/ +class BEAST_API XmlDocument +{ +public: + //============================================================================== + /** Creates an XmlDocument from the xml text. + The text doesn't actually get parsed until the getDocumentElement() method is called. + */ + XmlDocument (const String& documentText); + + /** Creates an XmlDocument from a file. + The text doesn't actually get parsed until the getDocumentElement() method is called. + */ + XmlDocument (const File& file); + + /** Destructor. */ + ~XmlDocument(); + + //============================================================================== + /** Creates an XmlElement object to represent the main document node. + + This method will do the actual parsing of the text, and if there's a + parse error, it may returns nullptr (and you can find out the error using + the getLastParseError() method). + + See also the parse() methods, which provide a shorthand way to quickly + parse a file or string. + + @param onlyReadOuterDocumentElement if true, the parser will only read the + first section of the file, and will only + return the outer document element - this + allows quick checking of large files to + see if they contain the correct type of + tag, without having to parse the entire file + @returns a new XmlElement which the caller will need to delete, or null if + there was an error. + @see getLastParseError + */ + XmlElement* getDocumentElement (bool onlyReadOuterDocumentElement = false); + + /** Returns the parsing error that occurred the last time getDocumentElement was called. + + @returns the error, or an empty string if there was no error. + */ + const String& getLastParseError() const noexcept; + + /** Sets an input source object to use for parsing documents that reference external entities. + + If the document has been created from a file, this probably won't be needed, but + if you're parsing some text and there might be a DTD that references external + files, you may need to create a custom input source that can retrieve the + other files it needs. + + The object that is passed-in will be deleted automatically when no longer needed. + + @see InputSource + */ + void setInputSource (InputSource* newSource) noexcept; + + /** Sets a flag to change the treatment of empty text elements. + + If this is true (the default state), then any text elements that contain only + whitespace characters will be ingored during parsing. If you need to catch + whitespace-only text, then you should set this to false before calling the + getDocumentElement() method. + */ + void setEmptyTextElementsIgnored (bool shouldBeIgnored) noexcept; + + //============================================================================== + /** A handy static method that parses a file. + This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. + @returns a new XmlElement which the caller will need to delete, or null if there was an error. + */ + static XmlElement* parse (const File& file); + + /** A handy static method that parses some XML data. + This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. + @returns a new XmlElement which the caller will need to delete, or null if there was an error. + */ + static XmlElement* parse (const String& xmlData); + + + //============================================================================== +private: + String originalText; + String::CharPointerType input; + bool outOfData, errorOccurred; + + String lastError, dtdText; + StringArray tokenisedDTD; + bool needToLoadDTD, ignoreEmptyTextElements; + ScopedPointer inputSource; + + void setLastError (const String& desc, bool carryOn); + void skipHeader(); + void skipNextWhiteSpace(); + beast_wchar readNextChar() noexcept; + XmlElement* readNextElement (bool alsoParseSubElements); + void readChildElements (XmlElement* parent); + int findNextTokenLength() noexcept; + void readQuotedString (String& result); + void readEntity (String& result); + + String getFileContents (const String& filename) const; + String expandEntity (const String& entity); + String expandExternalEntity (const String& entity); + String getParameterEntity (const String& entity); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) +}; + + +#endif // BEAST_XMLDOCUMENT_BEASTHEADER diff --git a/modules/beast_core/xml/beast_XmlElement.cpp b/modules/beast_core/xml/beast_XmlElement.cpp new file mode 100644 index 0000000000..3b51fa78bf --- /dev/null +++ b/modules/beast_core/xml/beast_XmlElement.cpp @@ -0,0 +1,824 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept + : name (other.name), + value (other.value) +{ +} + +XmlElement::XmlAttributeNode::XmlAttributeNode (const String& n, const String& v) noexcept + : name (n), value (v) +{ + #if BEAST_DEBUG + // this checks whether the attribute name string contains any illegal characters.. + for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t) + bassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':'); + #endif +} + +inline bool XmlElement::XmlAttributeNode::hasName (const String& nameToMatch) const noexcept +{ + return name.equalsIgnoreCase (nameToMatch); +} + +//============================================================================== +XmlElement::XmlElement (const String& tag) noexcept + : tagName (tag) +{ + // the tag name mustn't be empty, or it'll look like a text element! + bassert (tag.containsNonWhitespaceChars()) + + // The tag can't contain spaces or other characters that would create invalid XML! + bassert (! tag.containsAnyOf (" <>/&")); +} + +XmlElement::XmlElement (int /*dummy*/) noexcept +{ +} + +XmlElement::XmlElement (const XmlElement& other) + : tagName (other.tagName) +{ + copyChildrenAndAttributesFrom (other); +} + +XmlElement& XmlElement::operator= (const XmlElement& other) +{ + if (this != &other) + { + removeAllAttributes(); + deleteAllChildElements(); + + tagName = other.tagName; + + copyChildrenAndAttributesFrom (other); + } + + return *this; +} + +#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS +XmlElement::XmlElement (XmlElement&& other) noexcept + : nextListItem (static_cast &&> (other.nextListItem)), + firstChildElement (static_cast &&> (other.firstChildElement)), + attributes (static_cast &&> (other.attributes)), + tagName (static_cast (other.tagName)) +{ +} + +XmlElement& XmlElement::operator= (XmlElement&& other) noexcept +{ + bassert (this != &other); // hopefully the compiler should make this situation impossible! + + removeAllAttributes(); + deleteAllChildElements(); + + nextListItem = static_cast &&> (other.nextListItem); + firstChildElement = static_cast &&> (other.firstChildElement); + attributes = static_cast &&> (other.attributes); + tagName = static_cast (other.tagName); + + return *this; +} +#endif + +void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) +{ + bassert (firstChildElement.get() == nullptr); + firstChildElement.addCopyOfList (other.firstChildElement); + + bassert (attributes.get() == nullptr); + attributes.addCopyOfList (other.attributes); +} + +XmlElement::~XmlElement() noexcept +{ + firstChildElement.deleteAll(); + attributes.deleteAll(); +} + +//============================================================================== +namespace XmlOutputFunctions +{ + #if 0 // (These functions are just used to generate the lookup table used below) + bool isLegalXmlCharSlow (const beast_wchar character) noexcept + { + if ((character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9')) + return true; + + const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; + + do + { + if (((beast_wchar) (uint8) *t) == character) + return true; + } + while (*++t != 0); + + return false; + } + + void generateLegalCharLookupTable() + { + uint8 n[32] = { 0 }; + for (int i = 0; i < 256; ++i) + if (isLegalXmlCharSlow (i)) + n[i >> 3] |= (1 << (i & 7)); + + String s; + for (int i = 0; i < 32; ++i) + s << (int) n[i] << ", "; + + DBG (s); + } + #endif + + static bool isLegalXmlChar (const uint32 c) noexcept + { + static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, 255, 255, 191, 254, 255, 255, 127 }; + + return c < sizeof (legalChars) * 8 + && (legalChars [c >> 3] & (1 << (c & 7))) != 0; + } + + static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) + { + String::CharPointerType t (text.getCharPointer()); + + for (;;) + { + const uint32 character = (uint32) t.getAndAdvance(); + + if (character == 0) + break; + + if (isLegalXmlChar (character)) + { + outputStream << (char) character; + } + else + { + switch (character) + { + case '&': outputStream << "&"; break; + case '"': outputStream << """; break; + case '>': outputStream << ">"; break; + case '<': outputStream << "<"; break; + + case '\n': + case '\r': + if (! changeNewLines) + { + outputStream << (char) character; + break; + } + // Note: deliberate fall-through here! + default: + outputStream << "&#" << ((int) character) << ';'; + break; + } + } + } + } + + static void writeSpaces (OutputStream& out, const int numSpaces) + { + out.writeRepeatedByte (' ', numSpaces); + } +} + +void XmlElement::writeElementAsText (OutputStream& outputStream, + const int indentationLevel, + const int lineWrapLength) const +{ + using namespace XmlOutputFunctions; + + if (indentationLevel >= 0) + writeSpaces (outputStream, indentationLevel); + + if (! isTextElement()) + { + outputStream.writeByte ('<'); + outputStream << tagName; + + { + const int attIndent = indentationLevel + tagName.length() + 1; + int lineLen = 0; + + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + { + if (lineLen > lineWrapLength && indentationLevel >= 0) + { + outputStream << newLine; + writeSpaces (outputStream, attIndent); + lineLen = 0; + } + + const int64 startPos = outputStream.getPosition(); + outputStream.writeByte (' '); + outputStream << att->name; + outputStream.write ("=\"", 2); + escapeIllegalXmlChars (outputStream, att->value, true); + outputStream.writeByte ('"'); + lineLen += (int) (outputStream.getPosition() - startPos); + } + } + + if (firstChildElement != nullptr) + { + outputStream.writeByte ('>'); + + bool lastWasTextNode = false; + + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + { + if (child->isTextElement()) + { + escapeIllegalXmlChars (outputStream, child->getText(), false); + lastWasTextNode = true; + } + else + { + if (indentationLevel >= 0 && ! lastWasTextNode) + outputStream << newLine; + + child->writeElementAsText (outputStream, + lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength); + lastWasTextNode = false; + } + } + + if (indentationLevel >= 0 && ! lastWasTextNode) + { + outputStream << newLine; + writeSpaces (outputStream, indentationLevel); + } + + outputStream.write ("'); + } + else + { + outputStream.write ("/>", 2); + } + } + else + { + escapeIllegalXmlChars (outputStream, getText(), false); + } +} + +String XmlElement::createDocument (const String& dtdToUse, + const bool allOnOneLine, + const bool includeXmlHeader, + const String& encodingType, + const int lineWrapLength) const +{ + MemoryOutputStream mem (2048); + writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); + + return mem.toUTF8(); +} + +void XmlElement::writeToStream (OutputStream& output, + const String& dtdToUse, + const bool allOnOneLine, + const bool includeXmlHeader, + const String& encodingType, + const int lineWrapLength) const +{ + using namespace XmlOutputFunctions; + + if (includeXmlHeader) + { + output << ""; + + if (allOnOneLine) + output.writeByte (' '); + else + output << newLine << newLine; + } + + if (dtdToUse.isNotEmpty()) + { + output << dtdToUse; + + if (allOnOneLine) + output.writeByte (' '); + else + output << newLine; + } + + writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength); + + if (! allOnOneLine) + output << newLine; +} + +bool XmlElement::writeToFile (const File& file, + const String& dtdToUse, + const String& encodingType, + const int lineWrapLength) const +{ + TemporaryFile tempFile (file); + + { + FileOutputStream out (tempFile.getFile()); + + if (! out.openedOk()) + return false; + + writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength); + } + + return tempFile.overwriteTargetFileWithTemporary(); +} + +//============================================================================== +bool XmlElement::hasTagName (const String& possibleTagName) const noexcept +{ + const bool matches = tagName.equalsIgnoreCase (possibleTagName); + + // XML tags should be case-sensitive, so although this method allows a + // case-insensitive match to pass, you should try to avoid this. + bassert ((! matches) || tagName == possibleTagName); + + return matches; +} + +String XmlElement::getNamespace() const +{ + return tagName.upToFirstOccurrenceOf (":", false, false); +} + +String XmlElement::getTagNameWithoutNamespace() const +{ + return tagName.fromLastOccurrenceOf (":", false, false); +} + +bool XmlElement::hasTagNameIgnoringNamespace (const String& possibleTagName) const +{ + return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName; +} + +XmlElement* XmlElement::getNextElementWithTagName (const String& requiredTagName) const +{ + XmlElement* e = nextListItem; + + while (e != nullptr && ! e->hasTagName (requiredTagName)) + e = e->nextListItem; + + return e; +} + +//============================================================================== +int XmlElement::getNumAttributes() const noexcept +{ + return attributes.size(); +} + +const String& XmlElement::getAttributeName (const int index) const noexcept +{ + const XmlAttributeNode* const att = attributes [index]; + return att != nullptr ? att->name : String::empty; +} + +const String& XmlElement::getAttributeValue (const int index) const noexcept +{ + const XmlAttributeNode* const att = attributes [index]; + return att != nullptr ? att->value : String::empty; +} + +bool XmlElement::hasAttribute (const String& attributeName) const noexcept +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->hasName (attributeName)) + return true; + + return false; +} + +//============================================================================== +const String& XmlElement::getStringAttribute (const String& attributeName) const noexcept +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->hasName (attributeName)) + return att->value; + + return String::empty; +} + +String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->hasName (attributeName)) + return att->value; + + return defaultReturnValue; +} + +int XmlElement::getIntAttribute (const String& attributeName, const int defaultReturnValue) const +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->hasName (attributeName)) + return att->value.getIntValue(); + + return defaultReturnValue; +} + +double XmlElement::getDoubleAttribute (const String& attributeName, const double defaultReturnValue) const +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->hasName (attributeName)) + return att->value.getDoubleValue(); + + return defaultReturnValue; +} + +bool XmlElement::getBoolAttribute (const String& attributeName, const bool defaultReturnValue) const +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + { + if (att->hasName (attributeName)) + { + beast_wchar firstChar = att->value[0]; + + if (CharacterFunctions::isWhitespace (firstChar)) + firstChar = att->value.trimStart() [0]; + + return firstChar == '1' + || firstChar == 't' + || firstChar == 'y' + || firstChar == 'T' + || firstChar == 'Y'; + } + } + + return defaultReturnValue; +} + +bool XmlElement::compareAttribute (const String& attributeName, + const String& stringToCompareAgainst, + const bool ignoreCase) const noexcept +{ + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->hasName (attributeName)) + return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst) + : att->value == stringToCompareAgainst; + + return false; +} + +//============================================================================== +void XmlElement::setAttribute (const String& attributeName, const String& value) +{ + if (attributes == nullptr) + { + attributes = new XmlAttributeNode (attributeName, value); + } + else + { + for (XmlAttributeNode* att = attributes; ; att = att->nextListItem) + { + if (att->hasName (attributeName)) + { + att->value = value; + break; + } + + if (att->nextListItem == nullptr) + { + att->nextListItem = new XmlAttributeNode (attributeName, value); + break; + } + } + } +} + +void XmlElement::setAttribute (const String& attributeName, const int number) +{ + setAttribute (attributeName, String (number)); +} + +void XmlElement::setAttribute (const String& attributeName, const double number) +{ + setAttribute (attributeName, String (number)); +} + +void XmlElement::removeAttribute (const String& attributeName) noexcept +{ + for (LinkedListPointer* att = &attributes; + att->get() != nullptr; + att = &(att->get()->nextListItem)) + { + if (att->get()->hasName (attributeName)) + { + delete att->removeNext(); + break; + } + } +} + +void XmlElement::removeAllAttributes() noexcept +{ + attributes.deleteAll(); +} + +//============================================================================== +int XmlElement::getNumChildElements() const noexcept +{ + return firstChildElement.size(); +} + +XmlElement* XmlElement::getChildElement (const int index) const noexcept +{ + return firstChildElement [index].get(); +} + +XmlElement* XmlElement::getChildByName (const String& childName) const noexcept +{ + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + if (child->hasTagName (childName)) + return child; + + return nullptr; +} + +void XmlElement::addChildElement (XmlElement* const newNode) noexcept +{ + if (newNode != nullptr) + firstChildElement.append (newNode); +} + +void XmlElement::insertChildElement (XmlElement* const newNode, + int indexToInsertAt) noexcept +{ + if (newNode != nullptr) + { + removeChildElement (newNode, false); + firstChildElement.insertAtIndex (indexToInsertAt, newNode); + } +} + +XmlElement* XmlElement::createNewChildElement (const String& childTagName) +{ + XmlElement* const newElement = new XmlElement (childTagName); + addChildElement (newElement); + return newElement; +} + +bool XmlElement::replaceChildElement (XmlElement* const currentChildElement, + XmlElement* const newNode) noexcept +{ + if (newNode != nullptr) + { + if (LinkedListPointer* const p = firstChildElement.findPointerTo (currentChildElement)) + { + if (currentChildElement != newNode) + delete p->replaceNext (newNode); + + return true; + } + } + + return false; +} + +void XmlElement::removeChildElement (XmlElement* const childToRemove, + const bool shouldDeleteTheChild) noexcept +{ + if (childToRemove != nullptr) + { + firstChildElement.remove (childToRemove); + + if (shouldDeleteTheChild) + delete childToRemove; + } +} + +bool XmlElement::isEquivalentTo (const XmlElement* const other, + const bool ignoreOrderOfAttributes) const noexcept +{ + if (this != other) + { + if (other == nullptr || tagName != other->tagName) + return false; + + if (ignoreOrderOfAttributes) + { + int totalAtts = 0; + + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + { + if (! other->compareAttribute (att->name, att->value)) + return false; + + ++totalAtts; + } + + if (totalAtts != other->getNumAttributes()) + return false; + } + else + { + const XmlAttributeNode* thisAtt = attributes; + const XmlAttributeNode* otherAtt = other->attributes; + + for (;;) + { + if (thisAtt == nullptr || otherAtt == nullptr) + { + if (thisAtt == otherAtt) // both 0, so it's a match + break; + + return false; + } + + if (thisAtt->name != otherAtt->name + || thisAtt->value != otherAtt->value) + { + return false; + } + + thisAtt = thisAtt->nextListItem; + otherAtt = otherAtt->nextListItem; + } + } + + const XmlElement* thisChild = firstChildElement; + const XmlElement* otherChild = other->firstChildElement; + + for (;;) + { + if (thisChild == nullptr || otherChild == nullptr) + { + if (thisChild == otherChild) // both 0, so it's a match + break; + + return false; + } + + if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes)) + return false; + + thisChild = thisChild->nextListItem; + otherChild = otherChild->nextListItem; + } + } + + return true; +} + +void XmlElement::deleteAllChildElements() noexcept +{ + firstChildElement.deleteAll(); +} + +void XmlElement::deleteAllChildElementsWithTagName (const String& name) noexcept +{ + for (XmlElement* child = firstChildElement; child != nullptr;) + { + XmlElement* const nextChild = child->nextListItem; + + if (child->hasTagName (name)) + removeChildElement (child, true); + + child = nextChild; + } +} + +bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept +{ + return firstChildElement.contains (possibleChild); +} + +XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept +{ + if (this == elementToLookFor || elementToLookFor == nullptr) + return nullptr; + + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + { + if (elementToLookFor == child) + return this; + + if (XmlElement* const found = child->findParentElementOf (elementToLookFor)) + return found; + } + + return nullptr; +} + +void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept +{ + firstChildElement.copyToArray (elems); +} + +void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept +{ + XmlElement* e = firstChildElement = elems[0]; + + for (int i = 1; i < num; ++i) + { + e->nextListItem = elems[i]; + e = e->nextListItem; + } + + e->nextListItem = nullptr; +} + +//============================================================================== +bool XmlElement::isTextElement() const noexcept +{ + return tagName.isEmpty(); +} + +static const String beast_xmltextContentAttributeName ("text"); + +const String& XmlElement::getText() const noexcept +{ + bassert (isTextElement()); // you're trying to get the text from an element that + // isn't actually a text element.. If this contains text sub-nodes, you + // probably want to use getAllSubText instead. + + return getStringAttribute (beast_xmltextContentAttributeName); +} + +void XmlElement::setText (const String& newText) +{ + if (isTextElement()) + setAttribute (beast_xmltextContentAttributeName, newText); + else + jassertfalse; // you can only change the text in a text element, not a normal one. +} + +String XmlElement::getAllSubText() const +{ + if (isTextElement()) + return getText(); + + MemoryOutputStream mem (1024); + + for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + mem << child->getAllSubText(); + + return mem.toString(); +} + +String XmlElement::getChildElementAllSubText (const String& childTagName, + const String& defaultReturnValue) const +{ + if (const XmlElement* const child = getChildByName (childTagName)) + return child->getAllSubText(); + + return defaultReturnValue; +} + +XmlElement* XmlElement::createTextElement (const String& text) +{ + XmlElement* const e = new XmlElement ((int) 0); + e->setAttribute (beast_xmltextContentAttributeName, text); + return e; +} + +void XmlElement::addTextElement (const String& text) +{ + addChildElement (createTextElement (text)); +} + +void XmlElement::deleteAllTextElements() noexcept +{ + for (XmlElement* child = firstChildElement; child != nullptr;) + { + XmlElement* const next = child->nextListItem; + + if (child->isTextElement()) + removeChildElement (child, true); + + child = next; + } +} diff --git a/modules/beast_core/xml/beast_XmlElement.h b/modules/beast_core/xml/beast_XmlElement.h new file mode 100644 index 0000000000..9705b78ea8 --- /dev/null +++ b/modules/beast_core/xml/beast_XmlElement.h @@ -0,0 +1,734 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_XMLELEMENT_BEASTHEADER +#define BEAST_XMLELEMENT_BEASTHEADER + +#include "../text/beast_String.h" +#include "../streams/beast_OutputStream.h" +#include "../files/beast_File.h" +#include "../containers/beast_LinkedListPointer.h" + + +//============================================================================== +/** A handy macro to make it easy to iterate all the child elements in an XmlElement. + + The parentXmlElement should be a reference to the parent XML, and the childElementVariableName + will be the name of a pointer to each child element. + + E.g. @code + XmlElement* myParentXml = createSomeKindOfXmlDocument(); + + forEachXmlChildElement (*myParentXml, child) + { + if (child->hasTagName ("FOO")) + doSomethingWithXmlElement (child); + } + + @endcode + + @see forEachXmlChildElementWithTagName +*/ +#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \ +\ + for (beast::XmlElement* childElementVariableName = (parentXmlElement).getFirstChildElement(); \ + childElementVariableName != nullptr; \ + childElementVariableName = childElementVariableName->getNextElement()) + +/** A macro that makes it easy to iterate all the child elements of an XmlElement + which have a specified tag. + + This does the same job as the forEachXmlChildElement macro, but only for those + elements that have a particular tag name. + + The parentXmlElement should be a reference to the parent XML, and the childElementVariableName + will be the name of a pointer to each child element. The requiredTagName is the + tag name to match. + + E.g. @code + XmlElement* myParentXml = createSomeKindOfXmlDocument(); + + forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG") + { + // the child object is now guaranteed to be a element.. + doSomethingWithMYTAGElement (child); + } + + @endcode + + @see forEachXmlChildElement +*/ +#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \ +\ + for (beast::XmlElement* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \ + childElementVariableName != nullptr; \ + childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName)) + + +//============================================================================== +/** Used to build a tree of elements representing an XML document. + + An XML document can be parsed into a tree of XmlElements, each of which + represents an XML tag structure, and which may itself contain other + nested elements. + + An XmlElement can also be converted back into a text document, and has + lots of useful methods for manipulating its attributes and sub-elements, + so XmlElements can actually be used as a handy general-purpose data + structure. + + Here's an example of parsing some elements: @code + // check we're looking at the right kind of document.. + if (myElement->hasTagName ("ANIMALS")) + { + // now we'll iterate its sub-elements looking for 'giraffe' elements.. + forEachXmlChildElement (*myElement, e) + { + if (e->hasTagName ("GIRAFFE")) + { + // found a giraffe, so use some of its attributes.. + + String giraffeName = e->getStringAttribute ("name"); + int giraffeAge = e->getIntAttribute ("age"); + bool isFriendly = e->getBoolAttribute ("friendly"); + } + } + } + @endcode + + And here's an example of how to create an XML document from scratch: @code + // create an outer node called "ANIMALS" + XmlElement animalsList ("ANIMALS"); + + for (int i = 0; i < numAnimals; ++i) + { + // create an inner element.. + XmlElement* giraffe = new XmlElement ("GIRAFFE"); + + giraffe->setAttribute ("name", "nigel"); + giraffe->setAttribute ("age", 10); + giraffe->setAttribute ("friendly", true); + + // ..and add our new element to the parent node + animalsList.addChildElement (giraffe); + } + + // now we can turn the whole thing into a text document.. + String myXmlDoc = animalsList.createDocument (String::empty); + @endcode + + @see XmlDocument +*/ +class BEAST_API XmlElement +{ +public: + //============================================================================== + /** Creates an XmlElement with this tag name. */ + explicit XmlElement (const String& tagName) noexcept; + + /** Creates a (deep) copy of another element. */ + XmlElement (const XmlElement& other); + + /** Creates a (deep) copy of another element. */ + XmlElement& operator= (const XmlElement& other); + + #if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS + XmlElement (XmlElement&& other) noexcept; + XmlElement& operator= (XmlElement&& other) noexcept; + #endif + + /** Deleting an XmlElement will also delete all its child elements. */ + ~XmlElement() noexcept; + + //============================================================================== + /** Compares two XmlElements to see if they contain the same text and attiributes. + + The elements are only considered equivalent if they contain the same attiributes + with the same values, and have the same sub-nodes. + + @param other the other element to compare to + @param ignoreOrderOfAttributes if true, this means that two elements with the + same attributes in a different order will be + considered the same; if false, the attributes must + be in the same order as well + */ + bool isEquivalentTo (const XmlElement* other, + bool ignoreOrderOfAttributes) const noexcept; + + //============================================================================== + /** Returns an XML text document that represents this element. + + The string returned can be parsed to recreate the same XmlElement that + was used to create it. + + @param dtdToUse the DTD to add to the document + @param allOnOneLine if true, this means that the document will not contain any + linefeeds, so it'll be smaller but not very easy to read. + @param includeXmlHeader whether to add the ", this would return "MOOSE". + @see hasTagName + */ + inline const String& getTagName() const noexcept { return tagName; } + + /** Returns the namespace portion of the tag-name, or an empty string if none is specified. */ + String getNamespace() const; + + /** Returns the part of the tag-name that follows any namespace declaration. */ + String getTagNameWithoutNamespace() const; + + /** Tests whether this element has a particular tag name. + @param possibleTagName the tag name you're comparing it with + @see getTagName + */ + bool hasTagName (const String& possibleTagName) const noexcept; + + /** Tests whether this element has a particular tag name, ignoring any XML namespace prefix. + So a test for e.g. "xyz" will return true for "xyz" and also "foo:xyz", "bar::xyz", etc. + @see getTagName + */ + bool hasTagNameIgnoringNamespace (const String& possibleTagName) const; + + //============================================================================== + /** Returns the number of XML attributes this element contains. + + E.g. for an element such as \, this would + return 2. + */ + int getNumAttributes() const noexcept; + + /** Returns the name of one of the elements attributes. + + E.g. for an element such as \, then + getAttributeName(1) would return "antlers". + + @see getAttributeValue, getStringAttribute + */ + const String& getAttributeName (int attributeIndex) const noexcept; + + /** Returns the value of one of the elements attributes. + + E.g. for an element such as \, then + getAttributeName(1) would return "2". + + @see getAttributeName, getStringAttribute + */ + const String& getAttributeValue (int attributeIndex) const noexcept; + + //============================================================================== + // Attribute-handling methods.. + + /** Checks whether the element contains an attribute with a certain name. */ + bool hasAttribute (const String& attributeName) const noexcept; + + /** Returns the value of a named attribute. + + @param attributeName the name of the attribute to look up + */ + const String& getStringAttribute (const String& attributeName) const noexcept; + + /** Returns the value of a named attribute. + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + */ + String getStringAttribute (const String& attributeName, + const String& defaultReturnValue) const; + + /** Compares the value of a named attribute with a value passed-in. + + @param attributeName the name of the attribute to look up + @param stringToCompareAgainst the value to compare it with + @param ignoreCase whether the comparison should be case-insensitive + @returns true if the value of the attribute is the same as the string passed-in; + false if it's different (or if no such attribute exists) + */ + bool compareAttribute (const String& attributeName, + const String& stringToCompareAgainst, + bool ignoreCase = false) const noexcept; + + /** Returns the value of a named attribute as an integer. + + This will try to find the attribute and convert it to an integer (using + the String::getIntValue() method). + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + @see setAttribute + */ + int getIntAttribute (const String& attributeName, + int defaultReturnValue = 0) const; + + /** Returns the value of a named attribute as floating-point. + + This will try to find the attribute and convert it to an integer (using + the String::getDoubleValue() method). + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + @see setAttribute + */ + double getDoubleAttribute (const String& attributeName, + double defaultReturnValue = 0.0) const; + + /** Returns the value of a named attribute as a boolean. + + This will try to find the attribute and interpret it as a boolean. To do this, + it'll return true if the value is "1", "true", "y", etc, or false for other + values. + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + */ + bool getBoolAttribute (const String& attributeName, + bool defaultReturnValue = false) const; + + /** Adds a named attribute to the element. + + If the element already contains an attribute with this name, it's value will + be updated to the new value. If there's no such attribute yet, a new one will + be added. + + Note that there are other setAttribute() methods that take integers, + doubles, etc. to make it easy to store numbers. + + @param attributeName the name of the attribute to set + @param newValue the value to set it to + @see removeAttribute + */ + void setAttribute (const String& attributeName, + const String& newValue); + + /** Adds a named attribute to the element, setting it to an integer value. + + If the element already contains an attribute with this name, it's value will + be updated to the new value. If there's no such attribute yet, a new one will + be added. + + Note that there are other setAttribute() methods that take integers, + doubles, etc. to make it easy to store numbers. + + @param attributeName the name of the attribute to set + @param newValue the value to set it to + */ + void setAttribute (const String& attributeName, + int newValue); + + /** Adds a named attribute to the element, setting it to a floating-point value. + + If the element already contains an attribute with this name, it's value will + be updated to the new value. If there's no such attribute yet, a new one will + be added. + + Note that there are other setAttribute() methods that take integers, + doubles, etc. to make it easy to store numbers. + + @param attributeName the name of the attribute to set + @param newValue the value to set it to + */ + void setAttribute (const String& attributeName, + double newValue); + + /** Removes a named attribute from the element. + + @param attributeName the name of the attribute to remove + @see removeAllAttributes + */ + void removeAttribute (const String& attributeName) noexcept; + + /** Removes all attributes from this element. + */ + void removeAllAttributes() noexcept; + + //============================================================================== + // Child element methods.. + + /** Returns the first of this element's sub-elements. + + see getNextElement() for an example of how to iterate the sub-elements. + + @see forEachXmlChildElement + */ + XmlElement* getFirstChildElement() const noexcept { return firstChildElement; } + + /** Returns the next of this element's siblings. + + This can be used for iterating an element's sub-elements, e.g. + @code + XmlElement* child = myXmlDocument->getFirstChildElement(); + + while (child != nullptr) + { + ...do stuff with this child.. + + child = child->getNextElement(); + } + @endcode + + Note that when iterating the child elements, some of them might be + text elements as well as XML tags - use isTextElement() to work this + out. + + Also, it's much easier and neater to use this method indirectly via the + forEachXmlChildElement macro. + + @returns the sibling element that follows this one, or zero if this is the last + element in its parent + + @see getNextElement, isTextElement, forEachXmlChildElement + */ + inline XmlElement* getNextElement() const noexcept { return nextListItem; } + + /** Returns the next of this element's siblings which has the specified tag + name. + + This is like getNextElement(), but will scan through the list until it + finds an element with the given tag name. + + @see getNextElement, forEachXmlChildElementWithTagName + */ + XmlElement* getNextElementWithTagName (const String& requiredTagName) const; + + /** Returns the number of sub-elements in this element. + + @see getChildElement + */ + int getNumChildElements() const noexcept; + + /** Returns the sub-element at a certain index. + + It's not very efficient to iterate the sub-elements by index - see + getNextElement() for an example of how best to iterate. + + @returns the n'th child of this element, or nullptr if the index is out-of-range + @see getNextElement, isTextElement, getChildByName + */ + XmlElement* getChildElement (int index) const noexcept; + + /** Returns the first sub-element with a given tag-name. + + @param tagNameToLookFor the tag name of the element you want to find + @returns the first element with this tag name, or nullptr if none is found + @see getNextElement, isTextElement, getChildElement + */ + XmlElement* getChildByName (const String& tagNameToLookFor) const noexcept; + + //============================================================================== + /** Appends an element to this element's list of children. + + Child elements are deleted automatically when their parent is deleted, so + make sure the object that you pass in will not be deleted by anything else, + and make sure it's not already the child of another element. + + @see getFirstChildElement, getNextElement, getNumChildElements, + getChildElement, removeChildElement + */ + void addChildElement (XmlElement* newChildElement) noexcept; + + /** Inserts an element into this element's list of children. + + Child elements are deleted automatically when their parent is deleted, so + make sure the object that you pass in will not be deleted by anything else, + and make sure it's not already the child of another element. + + @param newChildNode the element to add + @param indexToInsertAt the index at which to insert the new element - if this is + below zero, it will be added to the end of the list + @see addChildElement, insertChildElement + */ + void insertChildElement (XmlElement* newChildNode, + int indexToInsertAt) noexcept; + + /** Creates a new element with the given name and returns it, after adding it + as a child element. + + This is a handy method that means that instead of writing this: + @code + XmlElement* newElement = new XmlElement ("foobar"); + myParentElement->addChildElement (newElement); + @endcode + + ..you could just write this: + @code + XmlElement* newElement = myParentElement->createNewChildElement ("foobar"); + @endcode + */ + XmlElement* createNewChildElement (const String& tagName); + + /** Replaces one of this element's children with another node. + + If the current element passed-in isn't actually a child of this element, + this will return false and the new one won't be added. Otherwise, the + existing element will be deleted, replaced with the new one, and it + will return true. + */ + bool replaceChildElement (XmlElement* currentChildElement, + XmlElement* newChildNode) noexcept; + + /** Removes a child element. + + @param childToRemove the child to look for and remove + @param shouldDeleteTheChild if true, the child will be deleted, if false it'll + just remove it + */ + void removeChildElement (XmlElement* childToRemove, + bool shouldDeleteTheChild) noexcept; + + /** Deletes all the child elements in the element. + + @see removeChildElement, deleteAllChildElementsWithTagName + */ + void deleteAllChildElements() noexcept; + + /** Deletes all the child elements with a given tag name. + + @see removeChildElement + */ + void deleteAllChildElementsWithTagName (const String& tagName) noexcept; + + /** Returns true if the given element is a child of this one. */ + bool containsChildElement (const XmlElement* possibleChild) const noexcept; + + /** Recursively searches all sub-elements to find one that contains the specified + child element. + */ + XmlElement* findParentElementOf (const XmlElement* elementToLookFor) noexcept; + + //============================================================================== + /** Sorts the child elements using a comparator. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (const XmlElement* first, const XmlElement* second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items which the comparator + says are equivalent will be kept in the order in which they + currently appear in the array. This is slower to perform, but + may be important in some cases. If it's false, a faster algorithm + is used, but equivalent elements may be rearranged. + */ + template + void sortChildElements (ElementComparator& comparator, + bool retainOrderOfEquivalentItems = false) + { + const int num = getNumChildElements(); + + if (num > 1) + { + HeapBlock elems ((size_t) num); + getChildElementsAsArray (elems); + sortArray (comparator, (XmlElement**) elems, 0, num - 1, retainOrderOfEquivalentItems); + reorderChildElements (elems, num); + } + } + + //============================================================================== + /** Returns true if this element is a section of text. + + Elements can either be an XML tag element or a secton of text, so this + is used to find out what kind of element this one is. + + @see getAllText, addTextElement, deleteAllTextElements + */ + bool isTextElement() const noexcept; + + /** Returns the text for a text element. + + Note that if you have an element like this: + + @codehello@endcode + + then calling getText on the "xyz" element won't return "hello", because that is + actually stored in a special text sub-element inside the xyz element. To get the + "hello" string, you could either call getText on the (unnamed) sub-element, or + use getAllSubText() to do this automatically. + + Note that leading and trailing whitespace will be included in the string - to remove + if, just call String::trim() on the result. + + @see isTextElement, getAllSubText, getChildElementAllSubText + */ + const String& getText() const noexcept; + + /** Sets the text in a text element. + + Note that this is only a valid call if this element is a text element. If it's + not, then no action will be performed. If you're trying to add text inside a normal + element, you probably want to use addTextElement() instead. + */ + void setText (const String& newText); + + /** Returns all the text from this element's child nodes. + + This iterates all the child elements and when it finds text elements, + it concatenates their text into a big string which it returns. + + E.g. @codehello there world@endcode + if you called getAllSubText on the "xyz" element, it'd return "hello there world". + + Note that leading and trailing whitespace will be included in the string - to remove + if, just call String::trim() on the result. + + @see isTextElement, getChildElementAllSubText, getText, addTextElement + */ + String getAllSubText() const; + + /** Returns all the sub-text of a named child element. + + If there is a child element with the given tag name, this will return + all of its sub-text (by calling getAllSubText() on it). If there is + no such child element, this will return the default string passed-in. + + @see getAllSubText + */ + String getChildElementAllSubText (const String& childTagName, + const String& defaultReturnValue) const; + + /** Appends a section of text to this element. + + @see isTextElement, getText, getAllSubText + */ + void addTextElement (const String& text); + + /** Removes all the text elements from this element. + + @see isTextElement, getText, getAllSubText, addTextElement + */ + void deleteAllTextElements() noexcept; + + /** Creates a text element that can be added to a parent element. + */ + static XmlElement* createTextElement (const String& text); + + //============================================================================== +private: + struct XmlAttributeNode + { + XmlAttributeNode (const XmlAttributeNode&) noexcept; + XmlAttributeNode (const String& name, const String& value) noexcept; + + LinkedListPointer nextListItem; + String name, value; + + bool hasName (const String&) const noexcept; + + private: + XmlAttributeNode& operator= (const XmlAttributeNode&); + }; + + friend class XmlDocument; + friend class LinkedListPointer ; + friend class LinkedListPointer ; + friend class LinkedListPointer ::Appender; + + LinkedListPointer nextListItem; + LinkedListPointer firstChildElement; + LinkedListPointer attributes; + String tagName; + + XmlElement (int) noexcept; + void copyChildrenAndAttributesFrom (const XmlElement&); + void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const; + void getChildElementsAsArray (XmlElement**) const noexcept; + void reorderChildElements (XmlElement**, int) noexcept; + + BEAST_LEAK_DETECTOR (XmlElement) +}; + + +#endif // BEAST_XMLELEMENT_BEASTHEADER diff --git a/modules/beast_core/zip/beast_GZIPCompressorOutputStream.cpp b/modules/beast_core/zip/beast_GZIPCompressorOutputStream.cpp new file mode 100644 index 0000000000..0025d9fdb8 --- /dev/null +++ b/modules/beast_core/zip/beast_GZIPCompressorOutputStream.cpp @@ -0,0 +1,213 @@ +/* + ============================================================================== + + This file is part of the beast_core module of the BEAST library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the beast_core module! + All other BEAST modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.beast.com + + ============================================================================== +*/ + +class GZIPCompressorOutputStream::GZIPCompressorHelper +{ +public: + GZIPCompressorHelper (const int compressionLevel, const int windowBits) + : compLevel ((compressionLevel < 1 || compressionLevel > 9) ? -1 : compressionLevel), + isFirstDeflate (true), + streamIsValid (false), + finished (false) + { + using namespace zlibNamespace; + zerostruct (stream); + + streamIsValid = (deflateInit2 (&stream, compLevel, Z_DEFLATED, + windowBits != 0 ? windowBits : MAX_WBITS, + 8, strategy) == Z_OK); + } + + ~GZIPCompressorHelper() + { + if (streamIsValid) + zlibNamespace::deflateEnd (&stream); + } + + bool write (const uint8* data, size_t dataSize, OutputStream& out) + { + // When you call flush() on a gzip stream, the stream is closed, and you can + // no longer continue to write data to it! + bassert (! finished); + + while (dataSize > 0) + if (! doNextBlock (data, dataSize, out, Z_NO_FLUSH)) + return false; + + return true; + } + + void finish (OutputStream& out) + { + const uint8* data = nullptr; + size_t dataSize = 0; + + while (! finished) + doNextBlock (data, dataSize, out, Z_FINISH); + } + +private: + enum { strategy = 0 }; + + zlibNamespace::z_stream stream; + const int compLevel; + bool isFirstDeflate, streamIsValid, finished; + zlibNamespace::Bytef buffer[32768]; + + bool doNextBlock (const uint8*& data, size_t& dataSize, OutputStream& out, const int flushMode) + { + using namespace zlibNamespace; + + if (streamIsValid) + { + stream.next_in = const_cast (data); + stream.next_out = buffer; + stream.avail_in = (z_uInt) dataSize; + stream.avail_out = (z_uInt) sizeof (buffer); + + const int result = isFirstDeflate ? deflateParams (&stream, compLevel, strategy) + : deflate (&stream, flushMode); + isFirstDeflate = false; + + switch (result) + { + case Z_STREAM_END: + finished = true; + // Deliberate fall-through.. + case Z_OK: + { + data += dataSize - stream.avail_in; + dataSize = stream.avail_in; + const ssize_t bytesDone = sizeof (buffer) - (ssize_t) stream.avail_out; + return bytesDone <= 0 || out.write (buffer, (size_t) bytesDone); + } + + default: + break; + } + } + + return false; + } + + BEAST_DECLARE_NON_COPYABLE (GZIPCompressorHelper) +}; + +//============================================================================== +GZIPCompressorOutputStream::GZIPCompressorOutputStream (OutputStream* const out, + const int compressionLevel, + const bool deleteDestStream, + const int windowBits) + : destStream (out, deleteDestStream), + helper (new GZIPCompressorHelper (compressionLevel, windowBits)) +{ + bassert (out != nullptr); +} + +GZIPCompressorOutputStream::~GZIPCompressorOutputStream() +{ + flush(); +} + +void GZIPCompressorOutputStream::flush() +{ + helper->finish (*destStream); + destStream->flush(); +} + +bool GZIPCompressorOutputStream::write (const void* destBuffer, size_t howMany) +{ + bassert (destBuffer != nullptr && (ssize_t) howMany >= 0); + + return helper->write (static_cast (destBuffer), howMany, *destStream); +} + +int64 GZIPCompressorOutputStream::getPosition() +{ + return destStream->getPosition(); +} + +bool GZIPCompressorOutputStream::setPosition (int64 /*newPosition*/) +{ + jassertfalse; // can't do it! + return false; +} + +//============================================================================== +#if BEAST_UNIT_TESTS + +class GZIPTests : public UnitTest +{ +public: + GZIPTests() : UnitTest ("GZIP") {} + + void runTest() + { + beginTest ("GZIP"); + Random rng; + + for (int i = 100; --i >= 0;) + { + MemoryOutputStream original, compressed, uncompressed; + + { + GZIPCompressorOutputStream zipper (&compressed, rng.nextInt (10), false); + + for (int j = rng.nextInt (100); --j >= 0;) + { + MemoryBlock data ((unsigned int) (rng.nextInt (2000) + 1)); + + for (int k = (int) data.getSize(); --k >= 0;) + data[k] = (char) rng.nextInt (255); + + original << data; + zipper << data; + } + } + + { + MemoryInputStream compressedInput (compressed.getData(), compressed.getDataSize(), false); + GZIPDecompressorInputStream unzipper (compressedInput); + + uncompressed << unzipper; + } + + expectEquals ((int) uncompressed.getDataSize(), + (int) original.getDataSize()); + + if (original.getDataSize() == uncompressed.getDataSize()) + expect (memcmp (uncompressed.getData(), + original.getData(), + original.getDataSize()) == 0); + } + } +}; + +static GZIPTests gzipTests; + +#endif diff --git a/modules/beast_core/zip/beast_GZIPCompressorOutputStream.h b/modules/beast_core/zip/beast_GZIPCompressorOutputStream.h new file mode 100644 index 0000000000..6d7a66d6f5 --- /dev/null +++ b/modules/beast_core/zip/beast_GZIPCompressorOutputStream.h @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the beast_core module of the BEAST library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the beast_core module! + All other BEAST modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.beast.com + + ============================================================================== +*/ + +#ifndef BEAST_GZIPCOMPRESSOROUTPUTSTREAM_BEASTHEADER +#define BEAST_GZIPCOMPRESSOROUTPUTSTREAM_BEASTHEADER + +#include "../streams/beast_OutputStream.h" +#include "../memory/beast_OptionalScopedPointer.h" +#include "../memory/beast_HeapBlock.h" + + +//============================================================================== +/** + A stream which uses zlib to compress the data written into it. + + Important note: When you call flush() on a GZIPCompressorOutputStream, + the gzip data is closed - this means that no more data can be written to + it, and any subsequent attempts to call write() will cause an assertion. + + @see GZIPDecompressorInputStream +*/ +class BEAST_API GZIPCompressorOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates a compression stream. + + @param destStream the stream into which the compressed data should + be written + @param compressionLevel how much to compress the data, between 1 and 9, where + 1 is the fastest/lowest compression, and 9 is the + slowest/highest compression. Any value outside this range + indicates that a default compression level should be used. + @param deleteDestStreamWhenDestroyed whether or not to delete the destStream object when + this stream is destroyed + @param windowBits this is used internally to change the window size used + by zlib - leave it as 0 unless you specifically need to set + its value for some reason + */ + GZIPCompressorOutputStream (OutputStream* destStream, + int compressionLevel = 0, + bool deleteDestStreamWhenDestroyed = false, + int windowBits = 0); + + /** Destructor. */ + ~GZIPCompressorOutputStream(); + + //============================================================================== + /** Flushes and closes the stream. + Note that unlike most streams, when you call flush() on a GZIPCompressorOutputStream, + the stream is closed - this means that no more data can be written to it, and any + subsequent attempts to call write() will cause an assertion. + */ + void flush(); + + int64 getPosition(); + bool setPosition (int64 newPosition); + bool write (const void* destBuffer, size_t howMany); + + /** These are preset values that can be used for the constructor's windowBits paramter. + For more info about this, see the zlib documentation for its windowBits parameter. + */ + enum WindowBitsValues + { + windowBitsRaw = -15, + windowBitsGZIP = 15 + 16 + }; + +private: + //============================================================================== + OptionalScopedPointer destStream; + + class GZIPCompressorHelper; + friend class ScopedPointer ; + ScopedPointer helper; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPCompressorOutputStream) +}; + +#endif // BEAST_GZIPCOMPRESSOROUTPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/zip/beast_GZIPDecompressorInputStream.cpp b/modules/beast_core/zip/beast_GZIPDecompressorInputStream.cpp new file mode 100644 index 0000000000..ad1c8d4dd0 --- /dev/null +++ b/modules/beast_core/zip/beast_GZIPDecompressorInputStream.cpp @@ -0,0 +1,290 @@ +/* + ============================================================================== + + This file is part of the beast_core module of the BEAST library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the beast_core module! + All other BEAST modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.beast.com + + ============================================================================== +*/ + +#if BEAST_MSVC + #pragma warning (push) + #pragma warning (disable: 4309 4305 4365) +#endif + +namespace zlibNamespace +{ + #if BEAST_INCLUDE_ZLIB_CODE + #if BEAST_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wconversion" + #pragma clang diagnostic ignored "-Wshadow" + #endif + + #undef OS_CODE + #undef fdopen + #define ZLIB_INTERNAL + #define NO_DUMMY_DECL + #include "zlib/zlib.h" + #include "zlib/adler32.c" + #include "zlib/compress.c" + #undef DO1 + #undef DO8 + #include "zlib/crc32.c" + #include "zlib/deflate.c" + #include "zlib/inffast.c" + #undef PULLBYTE + #undef LOAD + #undef RESTORE + #undef INITBITS + #undef NEEDBITS + #undef DROPBITS + #undef BYTEBITS + #include "zlib/inflate.c" + #include "zlib/inftrees.c" + #include "zlib/trees.c" + #include "zlib/zutil.c" + #undef Byte + #undef fdopen + #undef local + + #if BEAST_CLANG + #pragma clang diagnostic pop + #endif + #else + #include BEAST_ZLIB_INCLUDE_PATH + #endif +} + +#if BEAST_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +// internal helper object that holds the zlib structures so they don't have to be +// included publicly. +class GZIPDecompressorInputStream::GZIPDecompressHelper +{ +public: + GZIPDecompressHelper (const bool dontWrap) + : finished (true), + needsDictionary (false), + error (true), + streamIsValid (false), + data (nullptr), + dataSize (0) + { + using namespace zlibNamespace; + zerostruct (stream); + streamIsValid = (inflateInit2 (&stream, dontWrap ? -MAX_WBITS : MAX_WBITS) == Z_OK); + finished = error = ! streamIsValid; + } + + ~GZIPDecompressHelper() + { + using namespace zlibNamespace; + if (streamIsValid) + inflateEnd (&stream); + } + + bool needsInput() const noexcept { return dataSize <= 0; } + + void setInput (uint8* const data_, const size_t size) noexcept + { + data = data_; + dataSize = size; + } + + int doNextBlock (uint8* const dest, const unsigned int destSize) + { + using namespace zlibNamespace; + if (streamIsValid && data != nullptr && ! finished) + { + stream.next_in = data; + stream.next_out = dest; + stream.avail_in = (z_uInt) dataSize; + stream.avail_out = (z_uInt) destSize; + + switch (inflate (&stream, Z_PARTIAL_FLUSH)) + { + case Z_STREAM_END: + finished = true; + // deliberate fall-through + case Z_OK: + data += dataSize - stream.avail_in; + dataSize = (z_uInt) stream.avail_in; + return (int) (destSize - stream.avail_out); + + case Z_NEED_DICT: + needsDictionary = true; + data += dataSize - stream.avail_in; + dataSize = (size_t) stream.avail_in; + break; + + case Z_DATA_ERROR: + case Z_MEM_ERROR: + error = true; + + default: + break; + } + } + + return 0; + } + + bool finished, needsDictionary, error, streamIsValid; + + enum { gzipDecompBufferSize = 32768 }; + +private: + zlibNamespace::z_stream stream; + uint8* data; + size_t dataSize; + + BEAST_DECLARE_NON_COPYABLE (GZIPDecompressHelper) +}; + +//============================================================================== +GZIPDecompressorInputStream::GZIPDecompressorInputStream (InputStream* const sourceStream_, + const bool deleteSourceWhenDestroyed, + const bool noWrap_, + const int64 uncompressedStreamLength_) + : sourceStream (sourceStream_, deleteSourceWhenDestroyed), + uncompressedStreamLength (uncompressedStreamLength_), + noWrap (noWrap_), + isEof (false), + activeBufferSize (0), + originalSourcePos (sourceStream_->getPosition()), + currentPos (0), + buffer ((size_t) GZIPDecompressHelper::gzipDecompBufferSize), + helper (new GZIPDecompressHelper (noWrap_)) +{ +} + +GZIPDecompressorInputStream::GZIPDecompressorInputStream (InputStream& sourceStream_) + : sourceStream (&sourceStream_, false), + uncompressedStreamLength (-1), + noWrap (false), + isEof (false), + activeBufferSize (0), + originalSourcePos (sourceStream_.getPosition()), + currentPos (0), + buffer ((size_t) GZIPDecompressHelper::gzipDecompBufferSize), + helper (new GZIPDecompressHelper (false)) +{ +} + +GZIPDecompressorInputStream::~GZIPDecompressorInputStream() +{ +} + +int64 GZIPDecompressorInputStream::getTotalLength() +{ + return uncompressedStreamLength; +} + +int GZIPDecompressorInputStream::read (void* destBuffer, int howMany) +{ + bassert (destBuffer != nullptr && howMany >= 0); + + if (howMany > 0 && ! isEof) + { + int numRead = 0; + uint8* d = static_cast (destBuffer); + + while (! helper->error) + { + const int n = helper->doNextBlock (d, (unsigned int) howMany); + currentPos += n; + + if (n == 0) + { + if (helper->finished || helper->needsDictionary) + { + isEof = true; + return numRead; + } + + if (helper->needsInput()) + { + activeBufferSize = sourceStream->read (buffer, (int) GZIPDecompressHelper::gzipDecompBufferSize); + + if (activeBufferSize > 0) + { + helper->setInput (buffer, (size_t) activeBufferSize); + } + else + { + isEof = true; + return numRead; + } + } + } + else + { + numRead += n; + howMany -= n; + d += n; + + if (howMany <= 0) + return numRead; + } + } + } + + return 0; +} + +bool GZIPDecompressorInputStream::isExhausted() +{ + return helper->error || isEof; +} + +int64 GZIPDecompressorInputStream::getPosition() +{ + return currentPos; +} + +bool GZIPDecompressorInputStream::setPosition (int64 newPos) +{ + if (newPos < currentPos) + { + // to go backwards, reset the stream and start again.. + isEof = false; + activeBufferSize = 0; + currentPos = 0; + helper = new GZIPDecompressHelper (noWrap); + + sourceStream->setPosition (originalSourcePos); + } + + skipNextBytes (newPos - currentPos); + return true; +} + +// (This is used as a way for the zip file code to use the crc32 function without including zlib) +unsigned long beast_crc32 (unsigned long, const unsigned char*, unsigned); +unsigned long beast_crc32 (unsigned long crc, const unsigned char* buf, unsigned len) +{ + return zlibNamespace::crc32 (crc, buf, len); +} diff --git a/modules/beast_core/zip/beast_GZIPDecompressorInputStream.h b/modules/beast_core/zip/beast_GZIPDecompressorInputStream.h new file mode 100644 index 0000000000..8dfc79cff7 --- /dev/null +++ b/modules/beast_core/zip/beast_GZIPDecompressorInputStream.h @@ -0,0 +1,102 @@ +/* + ============================================================================== + + This file is part of the beast_core module of the BEAST library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the beast_core module! + All other BEAST modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.beast.com + + ============================================================================== +*/ + +#ifndef BEAST_GZIPDECOMPRESSORINPUTSTREAM_BEASTHEADER +#define BEAST_GZIPDECOMPRESSORINPUTSTREAM_BEASTHEADER + +#include "../streams/beast_InputStream.h" +#include "../memory/beast_OptionalScopedPointer.h" +#include "../memory/beast_HeapBlock.h" + + +//============================================================================== +/** + This stream will decompress a source-stream using zlib. + + Tip: if you're reading lots of small items from one of these streams, you + can increase the performance enormously by passing it through a + BufferedInputStream, so that it has to read larger blocks less often. + + @see GZIPCompressorOutputStream +*/ +class BEAST_API GZIPDecompressorInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a decompressor stream. + + @param sourceStream the stream to read from + @param deleteSourceWhenDestroyed whether or not to delete the source stream + when this object is destroyed + @param noWrap this is used internally by the ZipFile class + and should be ignored by user applications + @param uncompressedStreamLength if the creator knows the length that the + uncompressed stream will be, then it can supply this + value, which will be returned by getTotalLength() + */ + GZIPDecompressorInputStream (InputStream* sourceStream, + bool deleteSourceWhenDestroyed, + bool noWrap = false, + int64 uncompressedStreamLength = -1); + + /** Creates a decompressor stream. + + @param sourceStream the stream to read from - the source stream must not be + deleted until this object has been destroyed + */ + GZIPDecompressorInputStream (InputStream& sourceStream); + + /** Destructor. */ + ~GZIPDecompressorInputStream(); + + //============================================================================== + int64 getPosition(); + bool setPosition (int64 pos); + int64 getTotalLength(); + bool isExhausted(); + int read (void* destBuffer, int maxBytesToRead); + + + //============================================================================== +private: + OptionalScopedPointer sourceStream; + const int64 uncompressedStreamLength; + const bool noWrap; + bool isEof; + int activeBufferSize; + int64 originalSourcePos, currentPos; + HeapBlock buffer; + + class GZIPDecompressHelper; + friend class ScopedPointer ; + ScopedPointer helper; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPDecompressorInputStream) +}; + +#endif // BEAST_GZIPDECOMPRESSORINPUTSTREAM_BEASTHEADER diff --git a/modules/beast_core/zip/beast_ZipFile.cpp b/modules/beast_core/zip/beast_ZipFile.cpp new file mode 100644 index 0000000000..66bdf071ed --- /dev/null +++ b/modules/beast_core/zip/beast_ZipFile.cpp @@ -0,0 +1,597 @@ +/* + ============================================================================== + + This file is part of the beast_core module of the BEAST library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the beast_core module! + All other BEAST modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.beast.com + + ============================================================================== +*/ + +class ZipFile::ZipEntryHolder +{ +public: + ZipEntryHolder (const char* const buffer, const int fileNameLen) + { + entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); + + const int time = ByteOrder::littleEndianShort (buffer + 12); + const int date = ByteOrder::littleEndianShort (buffer + 14); + entry.fileTime = getFileTimeFromRawEncodings (time, date); + + compressed = ByteOrder::littleEndianShort (buffer + 10) != 0; + compressedSize = (size_t) ByteOrder::littleEndianInt (buffer + 20); + entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24); + + streamOffset = ByteOrder::littleEndianInt (buffer + 42); + } + + struct FileNameComparator + { + static int compareElements (const ZipEntryHolder* first, const ZipEntryHolder* second) + { + return first->entry.filename.compare (second->entry.filename); + } + }; + + ZipEntry entry; + size_t streamOffset; + size_t compressedSize; + bool compressed; + +private: + static Time getFileTimeFromRawEncodings (int time, int date) + { + const int year = 1980 + (date >> 9); + const int month = ((date >> 5) & 15) - 1; + const int day = date & 31; + const int hours = time >> 11; + const int minutes = (time >> 5) & 63; + const int seconds = (time & 31) << 1; + + return Time (year, month, day, hours, minutes, seconds); + } +}; + +//============================================================================== +namespace +{ + int findEndOfZipEntryTable (InputStream& input, int& numEntries) + { + BufferedInputStream in (input, 8192); + + in.setPosition (in.getTotalLength()); + int64 pos = in.getPosition(); + const int64 lowestPos = bmax ((int64) 0, pos - 1024); + + char buffer [32] = { 0 }; + + while (pos > lowestPos) + { + in.setPosition (pos - 22); + pos = in.getPosition(); + memcpy (buffer + 22, buffer, 4); + + if (in.read (buffer, 22) != 22) + return 0; + + for (int i = 0; i < 22; ++i) + { + if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50) + { + in.setPosition (pos + i); + in.read (buffer, 22); + numEntries = ByteOrder::littleEndianShort (buffer + 10); + + return (int) ByteOrder::littleEndianInt (buffer + 16); + } + } + } + + return 0; + } +} + +//============================================================================== +class ZipFile::ZipInputStream : public InputStream +{ +public: + ZipInputStream (ZipFile& zf, ZipFile::ZipEntryHolder& zei) + : file (zf), + zipEntryHolder (zei), + pos (0), + headerSize (0), + inputStream (zf.inputStream) + { + if (zf.inputSource != nullptr) + { + inputStream = streamToDelete = file.inputSource->createInputStream(); + } + else + { + #if BEAST_DEBUG + zf.streamCounter.numOpenStreams++; + #endif + } + + char buffer [30]; + + if (inputStream != nullptr + && inputStream->setPosition (zei.streamOffset) + && inputStream->read (buffer, 30) == 30 + && ByteOrder::littleEndianInt (buffer) == 0x04034b50) + { + headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26) + + ByteOrder::littleEndianShort (buffer + 28); + } + } + + ~ZipInputStream() + { + #if BEAST_DEBUG + if (inputStream != nullptr && inputStream == file.inputStream) + file.streamCounter.numOpenStreams--; + #endif + } + + int64 getTotalLength() + { + return zipEntryHolder.compressedSize; + } + + int read (void* buffer, int howMany) + { + if (headerSize <= 0) + return 0; + + howMany = (int) bmin ((int64) howMany, (int64) (zipEntryHolder.compressedSize - pos)); + + if (inputStream == nullptr) + return 0; + + int num; + + if (inputStream == file.inputStream) + { + const ScopedLock sl (file.lock); + inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize); + num = inputStream->read (buffer, howMany); + } + else + { + inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize); + num = inputStream->read (buffer, howMany); + } + + pos += num; + return num; + } + + bool isExhausted() + { + return headerSize <= 0 || pos >= (int64) zipEntryHolder.compressedSize; + } + + int64 getPosition() + { + return pos; + } + + bool setPosition (int64 newPos) + { + pos = blimit ((int64) 0, (int64) zipEntryHolder.compressedSize, newPos); + return true; + } + +private: + ZipFile& file; + ZipEntryHolder zipEntryHolder; + int64 pos; + int headerSize; + InputStream* inputStream; + ScopedPointer streamToDelete; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream) +}; + + +//============================================================================== +ZipFile::ZipFile (InputStream* const stream, const bool deleteStreamWhenDestroyed) + : inputStream (stream) +{ + if (deleteStreamWhenDestroyed) + streamToDelete = inputStream; + + init(); +} + +ZipFile::ZipFile (InputStream& stream) + : inputStream (&stream) +{ + init(); +} + +ZipFile::ZipFile (const File& file) + : inputStream (nullptr), + inputSource (new FileInputSource (file)) +{ + init(); +} + +ZipFile::ZipFile (InputSource* const source) + : inputStream (nullptr), + inputSource (source) +{ + init(); +} + +ZipFile::~ZipFile() +{ + entries.clear(); +} + +#if BEAST_DEBUG +ZipFile::OpenStreamCounter::~OpenStreamCounter() +{ + /* If you hit this assertion, it means you've created a stream to read one of the items in the + zipfile, but you've forgotten to delete that stream object before deleting the file.. + Streams can't be kept open after the file is deleted because they need to share the input + stream that is managed by the ZipFile object. + */ + bassert (numOpenStreams == 0); +} +#endif + +//============================================================================== +int ZipFile::getNumEntries() const noexcept +{ + return entries.size(); +} + +const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept +{ + if (ZipEntryHolder* const zei = entries [index]) + return &(zei->entry); + + return nullptr; +} + +int ZipFile::getIndexOfFileName (const String& fileName) const noexcept +{ + for (int i = 0; i < entries.size(); ++i) + if (entries.getUnchecked (i)->entry.filename == fileName) + return i; + + return -1; +} + +const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const noexcept +{ + return getEntry (getIndexOfFileName (fileName)); +} + +InputStream* ZipFile::createStreamForEntry (const int index) +{ + InputStream* stream = nullptr; + + if (ZipEntryHolder* const zei = entries[index]) + { + stream = new ZipInputStream (*this, *zei); + + if (zei->compressed) + { + stream = new GZIPDecompressorInputStream (stream, true, true, + zei->entry.uncompressedSize); + + // (much faster to unzip in big blocks using a buffer..) + stream = new BufferedInputStream (stream, 32768, true); + } + } + + return stream; +} + +InputStream* ZipFile::createStreamForEntry (const ZipEntry& entry) +{ + for (int i = 0; i < entries.size(); ++i) + if (&entries.getUnchecked (i)->entry == &entry) + return createStreamForEntry (i); + + return nullptr; +} + +void ZipFile::sortEntriesByFilename() +{ + ZipEntryHolder::FileNameComparator sorter; + entries.sort (sorter); +} + +//============================================================================== +void ZipFile::init() +{ + ScopedPointer toDelete; + InputStream* in = inputStream; + + if (inputSource != nullptr) + { + in = inputSource->createInputStream(); + toDelete = in; + } + + if (in != nullptr) + { + int numEntries = 0; + int pos = findEndOfZipEntryTable (*in, numEntries); + + if (pos >= 0 && pos < in->getTotalLength()) + { + const int size = (int) (in->getTotalLength() - pos); + + in->setPosition (pos); + MemoryBlock headerData; + + if (in->readIntoMemoryBlock (headerData, size) == size) + { + pos = 0; + + for (int i = 0; i < numEntries; ++i) + { + if (pos + 46 > size) + break; + + const char* const buffer = static_cast (headerData.getData()) + pos; + + const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); + + if (pos + 46 + fileNameLen > size) + break; + + entries.add (new ZipEntryHolder (buffer, fileNameLen)); + + pos += 46 + fileNameLen + + ByteOrder::littleEndianShort (buffer + 30) + + ByteOrder::littleEndianShort (buffer + 32); + } + } + } + } +} + +Result ZipFile::uncompressTo (const File& targetDirectory, + const bool shouldOverwriteFiles) +{ + for (int i = 0; i < entries.size(); ++i) + { + Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles)); + if (result.failed()) + return result; + } + + return Result::ok(); +} + +Result ZipFile::uncompressEntry (const int index, + const File& targetDirectory, + bool shouldOverwriteFiles) +{ + const ZipEntryHolder* zei = entries.getUnchecked (index); + + #if BEAST_WINDOWS + const String entryPath (zei->entry.filename); + #else + const String entryPath (zei->entry.filename.replaceCharacter ('\\', '/')); + #endif + + const File targetFile (targetDirectory.getChildFile (entryPath)); + + if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\')) + return targetFile.createDirectory(); // (entry is a directory, not a file) + + ScopedPointer in (createStreamForEntry (index)); + + if (in == nullptr) + return Result::fail ("Failed to open the zip file for reading"); + + if (targetFile.exists()) + { + if (! shouldOverwriteFiles) + return Result::ok(); + + if (! targetFile.deleteFile()) + return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName()); + } + + if (! targetFile.getParentDirectory().createDirectory()) + return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName()); + + { + FileOutputStream out (targetFile); + + if (out.failedToOpen()) + return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName()); + + out << *in; + } + + targetFile.setCreationTime (zei->entry.fileTime); + targetFile.setLastModificationTime (zei->entry.fileTime); + targetFile.setLastAccessTime (zei->entry.fileTime); + + return Result::ok(); +} + + +//============================================================================= +extern unsigned long beast_crc32 (unsigned long crc, const unsigned char*, unsigned len); + +class ZipFile::Builder::Item +{ +public: + Item (const File& f, const int compression, const String& storedPath) + : file (f), + storedPathname (storedPath.isEmpty() ? f.getFileName() : storedPath), + compressionLevel (compression), + compressedSize (0), + headerStart (0) + { + } + + bool writeData (OutputStream& target, const int64 overallStartPosition) + { + MemoryOutputStream compressedData; + + if (compressionLevel > 0) + { + GZIPCompressorOutputStream compressor (&compressedData, compressionLevel, false, + GZIPCompressorOutputStream::windowBitsRaw); + if (! writeSource (compressor)) + return false; + } + else + { + if (! writeSource (compressedData)) + return false; + } + + compressedSize = (int) compressedData.getDataSize(); + headerStart = (int) (target.getPosition() - overallStartPosition); + + target.writeInt (0x04034b50); + writeFlagsAndSizes (target); + target << storedPathname + << compressedData; + + return true; + } + + bool writeDirectoryEntry (OutputStream& target) + { + target.writeInt (0x02014b50); + target.writeShort (20); // version written + writeFlagsAndSizes (target); + target.writeShort (0); // comment length + target.writeShort (0); // start disk num + target.writeShort (0); // internal attributes + target.writeInt (0); // external attributes + target.writeInt (headerStart); + target << storedPathname; + + return true; + } + +private: + const File file; + String storedPathname; + int compressionLevel, compressedSize, headerStart; + unsigned long checksum; + + void writeTimeAndDate (OutputStream& target) const + { + const Time t (file.getLastModificationTime()); + target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11))); + target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9))); + } + + bool writeSource (OutputStream& target) + { + checksum = 0; + FileInputStream input (file); + + if (input.failedToOpen()) + return false; + + const int bufferSize = 2048; + HeapBlock buffer (bufferSize); + + while (! input.isExhausted()) + { + const int bytesRead = input.read (buffer, bufferSize); + + if (bytesRead < 0) + return false; + + checksum = beast_crc32 (checksum, buffer, (unsigned int) bytesRead); + target.write (buffer, (size_t) bytesRead); + } + + return true; + } + + void writeFlagsAndSizes (OutputStream& target) const + { + target.writeShort (10); // version needed + target.writeShort (0); // flags + target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0); + writeTimeAndDate (target); + target.writeInt ((int) checksum); + target.writeInt (compressedSize); + target.writeInt ((int) file.getSize()); + target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1); + target.writeShort (0); // extra field length + } + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item) +}; + +//============================================================================= +ZipFile::Builder::Builder() {} +ZipFile::Builder::~Builder() {} + +void ZipFile::Builder::addFile (const File& fileToAdd, const int compressionLevel, const String& storedPathName) +{ + items.add (new Item (fileToAdd, compressionLevel, storedPathName)); +} + +bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const +{ + const int64 fileStart = target.getPosition(); + + for (int i = 0; i < items.size(); ++i) + { + if (progress != nullptr) + *progress = (i + 0.5) / items.size(); + + if (! items.getUnchecked (i)->writeData (target, fileStart)) + return false; + } + + const int64 directoryStart = target.getPosition(); + + for (int i = 0; i < items.size(); ++i) + if (! items.getUnchecked (i)->writeDirectoryEntry (target)) + return false; + + const int64 directoryEnd = target.getPosition(); + + target.writeInt (0x06054b50); + target.writeShort (0); + target.writeShort (0); + target.writeShort ((short) items.size()); + target.writeShort ((short) items.size()); + target.writeInt ((int) (directoryEnd - directoryStart)); + target.writeInt ((int) (directoryStart - fileStart)); + target.writeShort (0); + + if (progress != nullptr) + *progress = 1.0; + + return true; +} diff --git a/modules/beast_core/zip/beast_ZipFile.h b/modules/beast_core/zip/beast_ZipFile.h new file mode 100644 index 0000000000..eabedab4c9 --- /dev/null +++ b/modules/beast_core/zip/beast_ZipFile.h @@ -0,0 +1,249 @@ +/* + ============================================================================== + + This file is part of the beast_core module of the BEAST library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the beast_core module! + All other BEAST modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.beast.com + + ============================================================================== +*/ + +#ifndef BEAST_ZIPFILE_BEASTHEADER +#define BEAST_ZIPFILE_BEASTHEADER + +#include "../files/beast_File.h" +#include "../streams/beast_InputSource.h" +#include "../threads/beast_CriticalSection.h" +#include "../containers/beast_OwnedArray.h" + + +//============================================================================== +/** + Decodes a ZIP file from a stream. + + This can enumerate the items in a ZIP file and can create suitable stream objects + to read each one. +*/ +class BEAST_API ZipFile +{ +public: + /** Creates a ZipFile based for a file. */ + explicit ZipFile (const File& file); + + //============================================================================== + /** Creates a ZipFile for a given stream. + + @param inputStream the stream to read from + @param deleteStreamWhenDestroyed if set to true, the object passed-in + will be deleted when this ZipFile object is deleted + */ + ZipFile (InputStream* inputStream, bool deleteStreamWhenDestroyed); + + /** Creates a ZipFile for a given stream. + The stream will not be owned or deleted by this class - if you want the ZipFile to + manage the stream's lifetime, use the other constructor. + */ + explicit ZipFile (InputStream& inputStream); + + /** Creates a ZipFile for an input source. + + The inputSource object will be owned by the zip file, which will delete + it later when not needed. + */ + explicit ZipFile (InputSource* inputSource); + + /** Destructor. */ + ~ZipFile(); + + //============================================================================== + /** + Contains information about one of the entries in a ZipFile. + + @see ZipFile::getEntry + */ + struct ZipEntry + { + /** The name of the file, which may also include a partial pathname. */ + String filename; + + /** The file's original size. */ + unsigned int uncompressedSize; + + /** The last time the file was modified. */ + Time fileTime; + }; + + //============================================================================== + /** Returns the number of items in the zip file. */ + int getNumEntries() const noexcept; + + /** Returns a structure that describes one of the entries in the zip file. + + This may return zero if the index is out of range. + + @see ZipFile::ZipEntry + */ + const ZipEntry* getEntry (int index) const noexcept; + + /** Returns the index of the first entry with a given filename. + + This uses a case-sensitive comparison to look for a filename in the + list of entries. It might return -1 if no match is found. + + @see ZipFile::ZipEntry + */ + int getIndexOfFileName (const String& fileName) const noexcept; + + /** Returns a structure that describes one of the entries in the zip file. + + This uses a case-sensitive comparison to look for a filename in the + list of entries. It might return 0 if no match is found. + + @see ZipFile::ZipEntry + */ + const ZipEntry* getEntry (const String& fileName) const noexcept; + + /** Sorts the list of entries, based on the filename. + */ + void sortEntriesByFilename(); + + //============================================================================== + /** Creates a stream that can read from one of the zip file's entries. + + The stream that is returned must be deleted by the caller (and + zero might be returned if a stream can't be opened for some reason). + + The stream must not be used after the ZipFile object that created + has been deleted. + */ + InputStream* createStreamForEntry (int index); + + /** Creates a stream that can read from one of the zip file's entries. + + The stream that is returned must be deleted by the caller (and + zero might be returned if a stream can't be opened for some reason). + + The stream must not be used after the ZipFile object that created + has been deleted. + */ + InputStream* createStreamForEntry (const ZipEntry& entry); + + //============================================================================== + /** Uncompresses all of the files in the zip file. + + This will expand all the entries into a target directory. The relative + paths of the entries are used. + + @param targetDirectory the root folder to uncompress to + @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones + @returns success if the file is successfully unzipped + */ + Result uncompressTo (const File& targetDirectory, + bool shouldOverwriteFiles = true); + + /** Uncompresses one of the entries from the zip file. + + This will expand the entry and write it in a target directory. The entry's path is used to + determine which subfolder of the target should contain the new file. + + @param index the index of the entry to uncompress - this must be a valid index + between 0 and (getNumEntries() - 1). + @param targetDirectory the root folder to uncompress into + @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones + @returns success if all the files are successfully unzipped + */ + Result uncompressEntry (int index, + const File& targetDirectory, + bool shouldOverwriteFiles = true); + + + //============================================================================== + /** Used to create a new zip file. + + Create a ZipFile::Builder object, and call its addFile() method to add some files, + then you can write it to a stream with write(). + + Currently this just stores the files with no compression.. That will be added + soon! + */ + class Builder + { + public: + Builder(); + ~Builder(); + + /** Adds a file while should be added to the archive. + The file isn't read immediately, all the files will be read later when the writeToStream() + method is called. + + The compressionLevel can be between 0 (no compression), and 9 (maximum compression). + If the storedPathName parameter is specified, you can customise the partial pathname that + will be stored for this file. + */ + void addFile (const File& fileToAdd, int compressionLevel, + const String& storedPathName = String::empty); + + /** Generates the zip file, writing it to the specified stream. + If the progress parameter is non-null, it will be updated with an approximate + progress status between 0 and 1.0 + */ + bool writeToStream (OutputStream& target, double* progress) const; + + //============================================================================== + private: + class Item; + friend class OwnedArray; + OwnedArray items; + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Builder) + }; + +private: + //============================================================================== + class ZipInputStream; + class ZipEntryHolder; + friend class ZipInputStream; + friend class ZipEntryHolder; + + OwnedArray entries; + CriticalSection lock; + InputStream* inputStream; + ScopedPointer streamToDelete; + ScopedPointer inputSource; + + #if BEAST_DEBUG + struct OpenStreamCounter + { + OpenStreamCounter() : numOpenStreams (0) {} + ~OpenStreamCounter(); + + int numOpenStreams; + }; + + OpenStreamCounter streamCounter; + #endif + + void init(); + + BEAST_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipFile) +}; + +#endif // BEAST_ZIPFILE_BEASTHEADER diff --git a/modules/beast_core/zip/zlib/README b/modules/beast_core/zip/zlib/README new file mode 100644 index 0000000000..758cc50020 --- /dev/null +++ b/modules/beast_core/zip/zlib/README @@ -0,0 +1,125 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.3 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) +and rfc1952.txt (gzip format). These documents are also available in other +formats from ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example +of the library is given in the file example.c which also tests that the library +is working correctly. Another example is given in the file minigzip.c. The +compression library itself is composed of all source files except example.c and +minigzip.c. + +To compile all files and run the test program, follow the instructions given at +the top of Makefile. In short "make test; make install" should work for most +machines. For Unix: "./configure; make test; make install". For MSDOS, use one +of the special makefiles such as Makefile.msc. For VMS, use make_vms.com. + +Questions about zlib should be sent to , or to Gilles Vollant + for the Windows DLL version. The zlib home page is +http://www.zlib.org or http://www.gzip.org/zlib/ Before reporting a problem, +please check this site to verify that you have the latest version of zlib; +otherwise get the latest version and check whether the problem still exists or +not. + +PLEASE read the zlib FAQ http://www.gzip.org/zlib/zlib_faq.html before asking +for help. + +Mark Nelson wrote an article about zlib for the Jan. 1997 +issue of Dr. Dobb's Journal; a copy of the article is available in +http://dogma.net/markn/articles/zlibtool/zlibtool.htm + +The changes made in version 1.2.3 are documented in the file ChangeLog. + +Unsupported third party contributions are provided in directory "contrib". + +A Java implementation of zlib is available in the Java Development Kit +http://java.sun.com/j2se/1.4.2/docs/api/java/util/zip/package-summary.html +See the zlib home page http://www.zlib.org for details. + +A Perl interface to zlib written by Paul Marquess is in the +CPAN (Comprehensive Perl Archive Network) sites +http://www.cpan.org/modules/by-module/Compress/ + +A Python interface to zlib written by A.M. Kuchling is +available in Python 1.5 and later versions, see +http://www.python.org/doc/lib/module-zlib.html + +A zlib binding for TCL written by Andreas Kupries is +availlable at http://www.oche.de/~akupries/soft/trf/trf_zip.html + +An experimental package to read and write files in .zip format, written on top +of zlib by Gilles Vollant , is available in the +contrib/minizip directory of zlib. + + +Notes for some targets: + +- For Windows DLL versions, please see win32/DLL_FAQ.txt + +- For 64-bit Irix, deflate.c must be compiled without any optimization. With + -O, one libpng test fails. The test works in 32 bit mode (with the -n32 + compiler flag). The compiler bug has been reported to SGI. + +- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works + when compiled with cc. + +- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is + necessary to get gzprintf working correctly. This is done by configure. + +- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with + other compilers. Use "make test" to check your compiler. + +- gzdopen is not supported on RISCOS, BEOS and by some Mac compilers. + +- For PalmOs, see http://palmzlib.sourceforge.net/ + +- When building a shared, i.e. dynamic library on Mac OS X, the library must be + installed before testing (do "make install" before "make test"), since the + library location is specified in the library. + + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate + and zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; + they are too numerous to cite here. + +Copyright notice: + + (C) 1995-2004 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* +receiving lengthy legal documents to sign. The sources are provided +for free but without warranty of any kind. The library has been +entirely written by Jean-loup Gailly and Mark Adler; it does not +include third-party code. + +If you redistribute modified sources, we would appreciate that you include +in the file ChangeLog history information documenting your changes. Please +read the FAQ for more information on the distribution of modified source +versions. diff --git a/modules/beast_core/zip/zlib/adler32.c b/modules/beast_core/zip/zlib/adler32.c new file mode 100644 index 0000000000..47ba65a76b --- /dev/null +++ b/modules/beast_core/zip/zlib/adler32.c @@ -0,0 +1,143 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: adler32.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +#define BASE 65521UL /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware */ +#ifdef NO_DIVIDE +# define MOD(a) \ + do { \ + if (a >= (BASE << 16)) a -= (BASE << 16); \ + if (a >= (BASE << 15)) a -= (BASE << 15); \ + if (a >= (BASE << 14)) a -= (BASE << 14); \ + if (a >= (BASE << 13)) a -= (BASE << 13); \ + if (a >= (BASE << 12)) a -= (BASE << 12); \ + if (a >= (BASE << 11)) a -= (BASE << 11); \ + if (a >= (BASE << 10)) a -= (BASE << 10); \ + if (a >= (BASE << 9)) a -= (BASE << 9); \ + if (a >= (BASE << 8)) a -= (BASE << 8); \ + if (a >= (BASE << 7)) a -= (BASE << 7); \ + if (a >= (BASE << 6)) a -= (BASE << 6); \ + if (a >= (BASE << 5)) a -= (BASE << 5); \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD4(a) \ + do { \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD4(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len) +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD4(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2) +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* the derivation of this formula is left as an exercise for the reader */ + rem = (unsigned)(len2 % BASE); + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 > BASE) sum1 -= BASE; + if (sum1 > BASE) sum1 -= BASE; + if (sum2 > (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 > BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} diff --git a/modules/beast_core/zip/zlib/compress.c b/modules/beast_core/zip/zlib/compress.c new file mode 100644 index 0000000000..edcd9940e3 --- /dev/null +++ b/modules/beast_core/zip/zlib/compress.c @@ -0,0 +1,70 @@ +/* compress.c -- compress a memory buffer + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: compress.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ +int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen, int level) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; +#ifdef MAXSEG_64K + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; +#endif + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit(&stream, level); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/* =========================================================================== + */ +int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen) +{ + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} + +/* =========================================================================== + If the default memLevel or windowBits for deflateInit() is changed, then + this function needs to be updated. + */ +uLong ZEXPORT compressBound (uLong sourceLen) +{ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + 11; +} diff --git a/modules/beast_core/zip/zlib/crc32.c b/modules/beast_core/zip/zlib/crc32.c new file mode 100644 index 0000000000..75c6203bb2 --- /dev/null +++ b/modules/beast_core/zip/zlib/crc32.c @@ -0,0 +1,407 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id: crc32.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Find a four-byte integer type for crc32_little() and crc32_big(). */ +#ifndef NOBYFOUR +# ifdef STDC /* need ANSI C limits.h to determine sizes */ +# include +# define BYFOUR +# if (UINT_MAX == 0xffffffffUL) + typedef unsigned int u4; +# else +# if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long u4; +# else +# if (USHRT_MAX == 0xffffffffUL) + typedef unsigned short u4; +# else +# undef BYFOUR /* can't find a four-byte integer type! */ +# endif +# endif +# endif +# endif /* STDC */ +#endif /* !NOBYFOUR */ + +/* Definitions for doing the crc four data bytes at a time. */ +#ifdef BYFOUR +# define REV(w) (((w)>>24)+(((w)>>8)&0xff00)+ \ + (((w)&0xff00)<<8)+(((w)&0xff)<<24)) + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local unsigned long FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const unsigned long FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + unsigned long c; + int n, k; + unsigned long poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0UL; + for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) + poly |= 1UL << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (unsigned long)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = REV(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = REV(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const unsigned long FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const unsigned long FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const unsigned long FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const unsigned long FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32 (unsigned long crc, const unsigned char FAR *buf, unsigned len) +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + u4 endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf, unsigned len) +{ + register u4 c; + register const u4 FAR *buf4; + + c = (u4)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big (unsigned long crc, const unsigned char FAR *buf, unsigned len) +{ + register u4 c; + register const u4 FAR *buf4; + + c = REV((u4)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(REV(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times (unsigned long *mat, unsigned long vec) +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square (unsigned long *square, unsigned long *mat) +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine (uLong crc1, uLong crc2, z_off_t len2) +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case */ + if (len2 == 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320L; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} diff --git a/modules/beast_core/zip/zlib/crc32.h b/modules/beast_core/zip/zlib/crc32.h new file mode 100644 index 0000000000..8053b6117c --- /dev/null +++ b/modules/beast_core/zip/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const unsigned long FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/modules/beast_core/zip/zlib/deflate.c b/modules/beast_core/zip/zlib/deflate.c new file mode 100644 index 0000000000..66a34dc3b0 --- /dev/null +++ b/modules/beast_core/zip/zlib/deflate.c @@ -0,0 +1,1679 @@ +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGEMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many people for bug reports and testing. + * + * REFERENCES + * + * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". + * Available in http://www.ietf.org/rfc/rfc1951.txt + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + */ + +/* @(#) $Id: deflate.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#include "deflate.h" + +const char deflate_copyright[] = + " deflate 1.2.3 Copyright 1995-2005 Jean-loup Gailly "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* =========================================================================== + * Function prototypes. + */ +typedef enum { + need_more, /* block not completed, need more input or more output */ + block_done, /* block flush performed */ + finish_started, /* finish started, need only more output at next deflate */ + finish_done /* finish done, accept no more input or output */ +} block_state; + +typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +/* Compression function. Returns the block state after the call. */ + +local void fill_window OF((deflate_state *s)); +local block_state deflate_stored OF((deflate_state *s, int flush)); +local block_state deflate_fast OF((deflate_state *s, int flush)); +#ifndef FASTEST +local block_state deflate_slow OF((deflate_state *s, int flush)); +#endif +local void lm_init OF((deflate_state *s)); +local void putShortMSB OF((deflate_state *s, uInt b)); +local void flush_pending OF((z_streamp strm)); +local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); +#ifndef FASTEST +#ifdef ASMV + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else +local uInt longest_match OF((deflate_state *s, IPos cur_match)); +#endif +#endif +local uInt longest_match_fast OF((deflate_state *s, IPos cur_match)); + +#ifdef DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif + +/* =========================================================================== + * Local data + */ + +#define NIL 0 +/* Tail of hash chains */ + +#ifndef TOO_FAR +# define TOO_FAR 4096 +#endif +/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +typedef struct config_s { + ush good_length; /* reduce lazy search above this match length */ + ush max_lazy; /* do not perform lazy search above this match length */ + ush nice_length; /* quit search above this match length */ + ush max_chain; + compress_func func; +} config; + +#ifdef FASTEST +local const config configuration_table[2] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */ +#else +local const config configuration_table[10] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, + +/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ +/* 5 */ {8, 16, 32, 32, deflate_slow}, +/* 6 */ {8, 16, 128, 128, deflate_slow}, +/* 7 */ {8, 32, 128, 256, deflate_slow}, +/* 8 */ {32, 128, 258, 1024, deflate_slow}, +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */ +#endif + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +#ifndef NO_DUMMY_DECL +struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ +#endif + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) + + +/* =========================================================================== + * Insert string str in the dictionary and set match_head to the previous head + * of the hash chain (the most recent string with same hash key). Return + * the previous length of the hash chain. + * If this file is compiled with -DFASTEST, the compression level is forced + * to 1, and no hash chains are maintained. + * IN assertion: all calls to to INSERT_STRING are made with consecutive + * input characters and the first MIN_MATCH bytes of str are valid + * (except for the last MIN_MATCH-1 bytes of the input file). + */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#endif + +/* =========================================================================== + * Initialize the hash table (avoiding 64K overflow for 16 bit systems). + * prev[] will be initialized on the fly. + */ +#define CLEAR_HASH(s) \ + s->head[s->hash_size-1] = NIL; \ + zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + +/* ========================================================================= */ +int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version, int stream_size) +{ + return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, version, stream_size); + /* To do: ignore strm->next_in if we use it as window */ +} + +/* ========================================================================= */ +int ZEXPORT deflateInit2_ (z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size) +{ + deflate_state *s; + int wrap = 1; + static const char my_version[] = ZLIB_VERSION; + + ushf *overlay; + /* We overlay pending_buf and d_buf+l_buf. This works since the average + * output size for (length,distance) codes is <= 24 bits. + */ + + if (version == Z_NULL || version[0] != my_version[0] || + stream_size != sizeof(z_stream)) { + return Z_VERSION_ERROR; + } + if (strm == Z_NULL) return Z_STREAM_ERROR; + + strm->msg = Z_NULL; + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } +#ifdef GZIP + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } +#endif + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ + s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); + if (s == Z_NULL) return Z_MEM_ERROR; + strm->state = (struct internal_state FAR *)s; + s->strm = strm; + + s->wrap = wrap; + s->gzhead = Z_NULL; + s->w_bits = windowBits; + s->w_size = 1 << s->w_bits; + s->w_mask = s->w_size - 1; + + s->hash_bits = memLevel + 7; + s->hash_size = 1 << s->hash_bits; + s->hash_mask = s->hash_size - 1; + s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + + s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); + s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); + s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + s->pending_buf = (uchf *) overlay; + s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + + if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || + s->pending_buf == Z_NULL) { + s->status = FINISH_STATE; + strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + deflateEnd (strm); + return Z_MEM_ERROR; + } + s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + + s->level = level; + s->strategy = strategy; + s->method = (Byte)method; + + return deflateReset(strm); +} + +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength) +{ + deflate_state *s; + uInt length = dictLength; + uInt n; + IPos hash_head = 0; + + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL || + strm->state->wrap == 2 || + (strm->state->wrap == 1 && strm->state->status != INIT_STATE)) + return Z_STREAM_ERROR; + + s = strm->state; + if (s->wrap) + strm->adler = adler32(strm->adler, dictionary, dictLength); + + if (length < MIN_MATCH) return Z_OK; + if (length > MAX_DIST(s)) { + length = MAX_DIST(s); + dictionary += dictLength - length; /* use the tail of the dictionary */ + } + zmemcpy(s->window, dictionary, length); + s->strstart = length; + s->block_start = (long)length; + + /* Insert all strings in the hash table (except for the last two bytes). + * s->lookahead stays null, so s->ins_h will be recomputed at the next + * call of fill_window. + */ + s->ins_h = s->window[0]; + UPDATE_HASH(s, s->ins_h, s->window[1]); + for (n = 0; n <= length - MIN_MATCH; n++) { + INSERT_STRING(s, n, hash_head); + } + + (void) hash_head; /* to make compiler happy */ + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateReset (z_streamp strm) +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) { + return Z_STREAM_ERROR; + } + + strm->total_in = strm->total_out = 0; + strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ + strm->data_type = Z_UNKNOWN; + + s = (deflate_state *)strm->state; + s->pending = 0; + s->pending_out = s->pending_buf; + + if (s->wrap < 0) { + s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */ + } + s->status = s->wrap ? INIT_STATE : BUSY_STATE; + strm->adler = +#ifdef GZIP + s->wrap == 2 ? crc32(0L, Z_NULL, 0) : +#endif + adler32(0L, Z_NULL, 0); + s->last_flush = Z_NO_FLUSH; + + _tr_init(s); + lm_init(s); + + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head) +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (strm->state->wrap != 2) return Z_STREAM_ERROR; + strm->state->gzhead = head; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePrime (z_streamp strm, int bits, int value) +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + strm->state->bi_valid = bits; + strm->state->bi_buf = (ush)(value & ((1 << bits) - 1)); + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateParams (z_streamp strm, int level, int strategy) +{ + deflate_state *s; + compress_func func; + int err = Z_OK; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + func = configuration_table[s->level].func; + + if (func != configuration_table[level].func && strm->total_in != 0) { + /* Flush the last buffer: */ + err = deflate(strm, Z_PARTIAL_FLUSH); + } + if (s->level != level) { + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return err; +} + +/* ========================================================================= */ +int ZEXPORT deflateTune (z_streamp strm, int good_length, int max_lazy, int nice_length, int max_chain) +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + s->good_match = good_length; + s->max_lazy_match = max_lazy; + s->nice_match = nice_length; + s->max_chain_length = max_chain; + return Z_OK; +} + +/* ========================================================================= + * For the default windowBits of 15 and memLevel of 8, this function returns + * a close to exact, as well as small, upper bound on the compressed size. + * They are coded as constants here for a reason--if the #define's are + * changed, then this function needs to be changed as well. The return + * value for 15 and 8 only works for those exact settings. + * + * For any setting other than those defaults for windowBits and memLevel, + * the value returned is a conservative worst case for the maximum expansion + * resulting from using fixed blocks instead of stored blocks, which deflate + * can emit on compressed data for some combinations of the parameters. + * + * This function could be more sophisticated to provide closer upper bounds + * for every combination of windowBits and memLevel, as well as wrap. + * But even the conservative upper bound of about 14% expansion does not + * seem onerous for output buffer allocation. + */ +uLong ZEXPORT deflateBound (z_streamp strm, uLong sourceLen) +{ + deflate_state *s; + uLong destLen; + + /* conservative upper bound */ + destLen = sourceLen + + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 11; + + /* if can't get parameters, return conservative bound */ + if (strm == Z_NULL || strm->state == Z_NULL) + return destLen; + + /* if not default parameters, return conservative bound */ + s = strm->state; + if (s->w_bits != 15 || s->hash_bits != 8 + 7) + return destLen; + + /* default settings: return tight bound for that case */ + return compressBound(sourceLen); +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB (deflate_state *s, uInt b) +{ + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->next_out buffer and copying into it. + * (See also read_buf()). + */ +local void flush_pending (z_streamp strm) +{ + unsigned len = strm->state->pending; + + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, strm->state->pending_out, len); + strm->next_out += len; + strm->state->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + strm->state->pending -= len; + if (strm->state->pending == 0) { + strm->state->pending_out = strm->state->pending_buf; + } +} + +/* ========================================================================= */ +int ZEXPORT deflate (z_streamp strm, int flush) +{ + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + flush > Z_FINISH || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); + + s->strm = strm; /* just in case */ + old_flush = s->last_flush; + s->last_flush = flush; + + /* Write the header */ + if (s->status == INIT_STATE) { +#ifdef GZIP + if (s->wrap == 2) { + strm->adler = crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (s->gzhead == NULL) { + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s->status = BUSY_STATE; + } + else { + put_byte(s, (s->gzhead->text ? 1 : 0) + + (s->gzhead->hcrc ? 2 : 0) + + (s->gzhead->extra == Z_NULL ? 0 : 4) + + (s->gzhead->name == Z_NULL ? 0 : 8) + + (s->gzhead->comment == Z_NULL ? 0 : 16) + ); + put_byte(s, (Byte)(s->gzhead->time & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff)); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, s->gzhead->os & 0xff); + if (s->gzhead->extra != NULL) { + put_byte(s, s->gzhead->extra_len & 0xff); + put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); + } + if (s->gzhead->hcrc) + strm->adler = crc32(strm->adler, s->pending_buf, + s->pending); + s->gzindex = 0; + s->status = EXTRA_STATE; + } + } + else +#endif + { + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags; + + if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) + level_flags = 0; + else if (s->level < 6) + level_flags = 1; + else if (s->level == 6) + level_flags = 2; + else + level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s->status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = adler32(0L, Z_NULL, 0); + } + } +#ifdef GZIP + if (s->status == EXTRA_STATE) { + if (s->gzhead->extra != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + + while (s->gzindex < (s->gzhead->extra_len & 0xffff)) { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) + break; + } + put_byte(s, s->gzhead->extra[s->gzindex]); + s->gzindex++; + } + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (s->gzindex == s->gzhead->extra_len) { + s->gzindex = 0; + s->status = NAME_STATE; + } + } + else + s->status = NAME_STATE; + } + if (s->status == NAME_STATE) { + if (s->gzhead->name != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->name[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) { + s->gzindex = 0; + s->status = COMMENT_STATE; + } + } + else + s->status = COMMENT_STATE; + } + if (s->status == COMMENT_STATE) { + if (s->gzhead->comment != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->comment[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) + s->status = HCRC_STATE; + } + else + s->status = HCRC_STATE; + } + if (s->status == HCRC_STATE) { + if (s->gzhead->hcrc) { + if (s->pending + 2 > s->pending_buf_size) + flush_pending(strm); + if (s->pending + 2 <= s->pending_buf_size) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + strm->adler = crc32(0L, Z_NULL, 0); + s->status = BUSY_STATE; + } + } + else + s->status = BUSY_STATE; + } +#endif + + /* Flush as much pending output as possible */ + if (s->pending != 0) { + flush_pending(strm); + if (strm->avail_out == 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s->last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm->avail_in == 0 && flush <= old_flush && + flush != Z_FINISH) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s->status == FINISH_STATE && strm->avail_in != 0) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm->avail_in != 0 || s->lookahead != 0 || + (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { + block_state bstate; + + bstate = (*(configuration_table[s->level].func))(s, flush); + + if (bstate == finish_started || bstate == finish_done) { + s->status = FINISH_STATE; + } + if (bstate == need_more || bstate == finish_started) { + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate == block_done) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(s); + } else { /* FULL_FLUSH or SYNC_FLUSH */ + _tr_stored_block(s, (char*)0, 0L, 0); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush == Z_FULL_FLUSH) { + CLEAR_HASH(s); /* forget history */ + } + } + flush_pending(strm); + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + Assert(strm->avail_out > 0, "bug2"); + + if (flush != Z_FINISH) return Z_OK; + if (s->wrap <= 0) return Z_STREAM_END; + + /* Write the trailer */ +#ifdef GZIP + if (s->wrap == 2) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 16) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 24) & 0xff)); + put_byte(s, (Byte)(strm->total_in & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 8) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 16) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 24) & 0xff)); + } + else +#endif + { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */ + return s->pending != 0 ? Z_OK : Z_STREAM_END; +} + +/* ========================================================================= */ +int ZEXPORT deflateEnd (z_streamp strm) +{ + int status; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + + status = strm->state->status; + if (status != INIT_STATE && + status != EXTRA_STATE && + status != NAME_STATE && + status != COMMENT_STATE && + status != HCRC_STATE && + status != BUSY_STATE && + status != FINISH_STATE) { + return Z_STREAM_ERROR; + } + + /* Deallocate in reverse order of allocations: */ + TRY_FREE(strm, strm->state->pending_buf); + TRY_FREE(strm, strm->state->head); + TRY_FREE(strm, strm->state->prev); + TRY_FREE(strm, strm->state->window); + + ZFREE(strm, strm->state); + strm->state = Z_NULL; + + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy (z_streamp dest, z_streamp source) +{ +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + zmemcpy(dest, source, sizeof(z_stream)); + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + zmemcpy(ds, ss, sizeof(deflate_state)); + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif /* MAXSEG_64K */ +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local int read_buf (z_streamp strm, Bytef *buf, unsigned size) +{ + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, strm->next_in, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, strm->next_in, len); + } +#endif + zmemcpy(buf, strm->next_in, len); + strm->next_in += len; + strm->total_in += len; + + return (int)len; +} + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init (deflate_state *s) +{ + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +#ifndef FASTEST +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +#endif +} + +#ifndef FASTEST +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +#ifndef ASMV +/* For 80x86 and 680x0, an optimized version will be provided in match.asm or + * match.S. The code will be functionally equivalent. + */ +local uInt longest_match(deflate_state *s, IPos cur_match) +{ + unsigned chain_length = s->max_chain_length;/* max hash chain length */ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + int best_len = s->prev_length; /* best match length so far */ + int nice_match = s->nice_match; /* stop if match long enough */ + IPos limit = s->strstart > (IPos)MAX_DIST(s) ? + s->strstart - (IPos)MAX_DIST(s) : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + Posf *prev = s->prev; + uInt wmask = s->w_mask; + +#ifdef UNALIGNED_OK + /* Compare two bytes at a time. Note: this is not always beneficial. + * Try with and without -DUNALIGNED_OK to check. + */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + register ush scan_start = *(ushf*)scan; + register ush scan_end = *(ushf*)(scan+best_len-1); +#else + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end = scan[best_len]; +#endif + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s->prev_length >= s->good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + Assert(cur_match < s->strstart, "no future"); + match = s->window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ +#if (defined(UNALIGNED_OK) && MAX_MATCH == 258) + /* This code assumes sizeof(unsigned short) == 2. Do not use + * UNALIGNED_OK if your compiler uses a different size. + */ + if (*(ushf*)(match+best_len-1) != scan_end || + *(ushf*)match != scan_start) continue; + + /* It is not necessary to compare scan[2] and match[2] since they are + * always equal when the other bytes match, given that the hash keys + * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at + * strstart+3, +5, ... up to strstart+257. We check for insufficient + * lookahead only every 4th comparison; the 128th check will be made + * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * necessary to put more guard bytes at the end of the window, or + * to check more often for insufficient lookahead. + */ + Assert(scan[2] == match[2], "scan[2]?"); + scan++, match++; + do { + } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + scan < strend); + /* The funny "do {}" generates better code on most compilers */ + + /* Here, scan <= window+strstart+257 */ + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + if (*scan == *match) scan++; + + len = (MAX_MATCH - 1) - (int)(strend-scan); + scan = strend - (MAX_MATCH-1); + +#else /* UNALIGNED_OK */ + + if (match[best_len] != scan_end || + match[best_len-1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + +#endif /* UNALIGNED_OK */ + + if (len > best_len) { + s->match_start = cur_match; + best_len = len; + if (len >= nice_match) break; +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else + scan_end1 = scan[best_len-1]; + scan_end = scan[best_len]; +#endif + } + } while ((cur_match = prev[cur_match & wmask]) > limit + && --chain_length != 0); + + if ((uInt)best_len <= s->lookahead) return (uInt)best_len; + return s->lookahead; +} +#endif /* ASMV */ +#endif /* FASTEST */ + +/* --------------------------------------------------------------------------- + * Optimized version for level == 1 or strategy == Z_RLE only + */ +local uInt longest_match_fast (deflate_state *s, IPos cur_match) +{ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + Assert(cur_match < s->strstart, "no future"); + + match = s->window + cur_match; + + /* Return failure if the match length is less than 2: + */ + if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match += 2; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + + if (len < MIN_MATCH) return MIN_MATCH - 1; + + s->match_start = cur_match; + return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead; +} + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(deflate_state *s, IPos start, IPos match, int length) +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else +# define check_match(s, start, match, length) +#endif /* DEBUG */ + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window (deflate_state *s) +{ + register unsigned n, m; + register Posf *p; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize+MAX_DIST(s)) { + + zmemcpy(s->window, s->window+wsize, (unsigned)wsize); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + /* %%% avoid this when Z_RLE */ + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + } while (--n); + + n = wsize; +#ifndef FASTEST + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +#endif + more += wsize; + } + if (s->strm->avail_in == 0) return; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead >= MIN_MATCH) { + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK_ONLY(s, eof) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (eof)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ +} + +/* Same but force premature exit if necessary. */ +#define FLUSH_BLOCK(s, eof) { \ + FLUSH_BLOCK_ONLY(s, eof); \ + if (s->strm->avail_out == 0) return (eof) ? finish_started : need_more; \ +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +local block_state deflate_stored(deflate_state *s, int flush) +{ + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + ulg max_block_size = 0xffff; + ulg max_start; + + if (max_block_size > s->pending_buf_size - 5) { + max_block_size = s->pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s->lookahead <= 1) { + + Assert(s->strstart < s->w_size+MAX_DIST(s) || + s->block_start >= (long)s->w_size, "slide too late"); + + fill_window(s); + if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more; + + if (s->lookahead == 0) break; /* flush the current block */ + } + Assert(s->block_start >= 0L, "block gone"); + + s->strstart += s->lookahead; + s->lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + max_start = s->block_start + max_block_size; + if (s->strstart == 0 || (ulg)s->strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s->lookahead = (uInt)(s->strstart - max_start); + s->strstart = (uInt)max_start; + FLUSH_BLOCK(s, 0); + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) { + FLUSH_BLOCK(s, 0); + } + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast(deflate_state *s, int flush) +{ + IPos hash_head = NIL; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ +#ifdef FASTEST + if ((s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) || + (s->strategy == Z_RLE && s->strstart - hash_head == 1)) { + s->match_length = longest_match_fast (s, hash_head); + } +#else + if (s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) { + s->match_length = longest_match (s, hash_head); + } else if (s->strategy == Z_RLE && s->strstart - hash_head == 1) { + s->match_length = longest_match_fast (s, hash_head); + } +#endif + /* longest_match() or longest_match_fast() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +#ifndef FASTEST +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +local block_state deflate_slow(deflate_state *s, int flush) +{ + IPos hash_head = NIL; /* head of hash chain */ + int bflush; /* set if current block must be flushed */ + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + */ + s->prev_length = s->match_length, s->prev_match = s->match_start; + s->match_length = MIN_MATCH-1; + + if (hash_head != NIL && s->prev_length < s->max_lazy_match && + s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + if (s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) { + s->match_length = longest_match (s, hash_head); + } else if (s->strategy == Z_RLE && s->strstart - hash_head == 1) { + s->match_length = longest_match_fast (s, hash_head); + } + /* longest_match() or longest_match_fast() sets match_start */ + + if (s->match_length <= 5 && (s->strategy == Z_FILTERED +#if TOO_FAR <= 32767 + || (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR) +#endif + )) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s->match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) { + uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + check_match(s, s->strstart-1, s->prev_match, s->prev_length); + + _tr_tally_dist(s, s->strstart -1 - s->prev_match, + s->prev_length - MIN_MATCH, bflush); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s->lookahead -= s->prev_length-1; + s->prev_length -= 2; + do { + if (++s->strstart <= max_insert) { + INSERT_STRING(s, s->strstart, hash_head); + } + } while (--s->prev_length != 0); + s->match_available = 0; + s->match_length = MIN_MATCH-1; + s->strstart++; + + if (bflush) FLUSH_BLOCK(s, 0); + + } else if (s->match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { + FLUSH_BLOCK_ONLY(s, 0); + } + s->strstart++; + s->lookahead--; + if (s->strm->avail_out == 0) return need_more; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s->match_available) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + s->match_available = 0; + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} +#endif /* FASTEST */ + +#if 0 +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +local block_state deflate_rle(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + uInt run; /* length of run */ + uInt max; /* maximum length of run */ + uInt prev; /* byte at distance one to match */ + Bytef *scan; /* scan for end of run */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest encodable run. + */ + if (s->lookahead < MAX_MATCH) { + fill_window(s); + if (s->lookahead < MAX_MATCH && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + run = 0; + if (s->strstart > 0) { /* if there is a previous byte, that is */ + max = s->lookahead < MAX_MATCH ? s->lookahead : MAX_MATCH; + scan = s->window + s->strstart - 1; + prev = *scan++; + do { + if (*scan++ != prev) + break; + } while (++run < max); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (run >= MIN_MATCH) { + check_match(s, s->strstart, s->strstart - 1, run); + _tr_tally_dist(s, 1, run - MIN_MATCH, bflush); + s->lookahead -= run; + s->strstart += run; + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} +#endif diff --git a/modules/beast_core/zip/zlib/deflate.h b/modules/beast_core/zip/zlib/deflate.h new file mode 100644 index 0000000000..1d2f1548ca --- /dev/null +++ b/modules/beast_core/zip/zlib/deflate.h @@ -0,0 +1,333 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-2004 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id: deflate.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef DEFLATE_H +#define DEFLATE_H + +#include "zutil.h" + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer creation by deflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip encoding + should be left enabled. */ +#ifndef NO_GZIP +# define GZIP +#endif + +#define NO_DUMMY_DECL + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define INIT_STATE 42 +#define EXTRA_STATE 69 +#define NAME_STATE 73 +#define COMMENT_STATE 91 +#define HCRC_STATE 103 +#define BUSY_STATE 113 +#define FINISH_STATE 666 +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + uInt pending; /* nb of bytes in the pending buffer */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + gz_headerp gzhead; /* gzip header information to write */ + uInt gzindex; /* where in extra, name, or comment */ + Byte method; /* STORED (for zip only) or DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to supress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + int last_eob_len; /* bit length of EOB code for last block */ + +#ifdef DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + + /* in trees.c */ +void _tr_init OF((deflate_state *s)); +int _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); +void _tr_align OF((deflate_state *s)); +void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch _length_code[]; + extern uch _dist_code[]; +#else + extern const uch _length_code[]; + extern const uch _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (length); \ + ush dist = (distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif /* DEFLATE_H */ diff --git a/modules/beast_core/zip/zlib/infback.c b/modules/beast_core/zip/zlib/infback.c new file mode 100644 index 0000000000..7d7990e315 --- /dev/null +++ b/modules/beast_core/zip/zlib/infback.c @@ -0,0 +1,611 @@ +/* infback.c -- inflate using a call-back interface + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + This code is largely copied from inflate.c. Normally either infback.o or + inflate.o would be linked into an application--not both. The interface + with inffast.c is retained so that optimized assembler-coded versions of + inflate_fast() can be used with either inflate.c or infback.c. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* function prototypes */ +local void fixedtables1 OF((struct inflate_state FAR *state)); + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + windowBits is in the range 8..15, and window is a user-supplied + window and output buffer that is 2**windowBits bytes. + */ +int ZEXPORT inflateBackInit_(z_streamp strm, int windowBits, unsigned char FAR *window, const char *version, int stream_size) +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL || + windowBits < 8 || windowBits > 15) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->dmax = 32768U; + state->wbits = windowBits; + state->wsize = 1U << windowBits; + state->window = window; + state->write = 0; + state->whave = 0; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables1 (struct inflate_state FAR *state) +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +/* Macros for inflateBack(): */ + +/* Load returned state from inflate_fast() */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Set state from registers for inflate_fast() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = state->window; \ + left = state->wsize; \ + state->whave = left; \ + if (out(out_desc, put, left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack(z_streamp strm, in_func in, void FAR *in_desc, out_func out, void FAR *out_desc) +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code thisx; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + state->mode = TYPE; + state->last = 0; + state->whave = 0; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = state->window; + left = state->wsize; + + /* Inflate until end of block marked as last */ + for (;;) + switch (state->mode) { + case TYPE: + /* determine and dispatch block type */ + if (state->last) { + BYTEBITS(); + state->mode = DONE; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables1(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + + /* copy stored block from input to output */ + while (state->length != 0) { + copy = state->length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.val < 16) { + NEEDBITS(thisx.bits); + DROPBITS(thisx.bits); + state->lens[state->have++] = thisx.val; + } + else { + if (thisx.val == 16) { + NEEDBITS(thisx.bits + 2); + DROPBITS(thisx.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (thisx.val == 17) { + NEEDBITS(thisx.bits + 3); + DROPBITS(thisx.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(thisx.bits + 7); + DROPBITS(thisx.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + + case LEN: + /* use inflate_fast() if we have enough input and output */ + if (have >= 6 && left >= 258) { + RESTORE(); + if (state->whave < state->wsize) + state->whave = state->wsize - left; + inflate_fast(strm, state->wsize); + LOAD(); + break; + } + + /* get a literal, length, or end-of-block code */ + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.op && (thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + state->length = (unsigned)thisx.val; + + /* process literal */ + if (thisx.op == 0) { + Tracevv((stderr, thisx.val >= 0x20 && thisx.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", thisx.val)); + ROOM(); + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + } + + /* process end of block */ + if (thisx.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + + /* invalid code */ + if (thisx.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + state->extra = (unsigned)(thisx.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + + /* get distance code */ + for (;;) { + thisx = state->distcode[BITS(state->distbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if ((thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + if (thisx.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)thisx.val; + + /* get distance extra bits, if any */ + state->extra = (unsigned)(thisx.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } + if (state->offset > state->wsize - (state->whave < state->wsize ? + left : 0)) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = state->wsize - state->offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - state->offset; + copy = left; + } + if (copy > state->length) copy = state->length; + state->length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (state->length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left)) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBackEnd (z_streamp strm) +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} diff --git a/modules/beast_core/zip/zlib/inffast.c b/modules/beast_core/zip/zlib/inffast.c new file mode 100644 index 0000000000..0ccbdb6bc5 --- /dev/null +++ b/modules/beast_core/zip/zlib/inffast.c @@ -0,0 +1,316 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast (z_streamp strm, unsigned start) +{ + struct inflate_state FAR *state; + unsigned char FAR *in; /* local strm->next_in */ + unsigned char FAR *last; /* while in < last, enough input available */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code thisx; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + write = state->write; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + thisx = lcode[hold & lmask]; + dolen: + op = (unsigned)(thisx.bits); + hold >>= op; + bits -= op; + op = (unsigned)(thisx.op); + if (op == 0) { /* literal */ + Tracevv((stderr, thisx.val >= 0x20 && thisx.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", thisx.val)); + PUP(out) = (unsigned char)(thisx.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(thisx.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + thisx = dcode[hold & dmask]; + dodist: + op = (unsigned)(thisx.bits); + hold >>= op; + bits -= op; + op = (unsigned)(thisx.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(thisx.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + from = window - OFF; + if (write == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (write < op) { /* wrap around window */ + from += wsize + write - op; + op -= write; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (write < len) { /* some from start of window */ + op = write; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += write - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + thisx = dcode[thisx.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + thisx = lcode[thisx.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and write == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ diff --git a/modules/beast_core/zip/zlib/inffast.h b/modules/beast_core/zip/zlib/inffast.h new file mode 100644 index 0000000000..1e88d2d97b --- /dev/null +++ b/modules/beast_core/zip/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void inflate_fast OF((z_streamp strm, unsigned start)); diff --git a/modules/beast_core/zip/zlib/inffixed.h b/modules/beast_core/zip/zlib/inffixed.h new file mode 100644 index 0000000000..75ed4b5978 --- /dev/null +++ b/modules/beast_core/zip/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. It + is part of the implementation of the compression library and + is subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; diff --git a/modules/beast_core/zip/zlib/inflate.c b/modules/beast_core/zip/zlib/inflate.c new file mode 100644 index 0000000000..eccaf41b3b --- /dev/null +++ b/modules/beast_core/zip/zlib/inflate.c @@ -0,0 +1,1339 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common write == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, unsigned out)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, + unsigned len)); + +int ZEXPORT inflateReset (z_streamp strm) +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + strm->adler = 1; /* to support ill-conceived Java test suite */ + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->wsize = 0; + state->whave = 0; + state->write = 0; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflatePrime (z_streamp strm, int bits, int value) +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, int stream_size) +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + if (windowBits < 0) { + state->wrap = 0; + windowBits = -windowBits; + } + else { + state->wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) windowBits &= 15; +#endif + } + if (windowBits < 8 || windowBits > 15) { + ZFREE(strm, state); + strm->state = Z_NULL; + return Z_STREAM_ERROR; + } + state->wbits = (unsigned)windowBits; + state->window = Z_NULL; + return inflateReset(strm); +} + +int ZEXPORT inflateInit_ (z_streamp strm, const char *version, int stream_size) +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables (struct inflate_state FAR *state) +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, + state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow (z_streamp strm, unsigned out) +{ + struct inflate_state FAR *state; + unsigned copy, dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->write = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + copy = out - strm->avail_out; + if (copy >= state->wsize) { + zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + state->write = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->write; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->write, strm->next_out - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, strm->next_out - copy, copy); + state->write = copy; + state->whave = state->wsize; + } + else { + state->write += dist; + if (state->write == state->wsize) state->write = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Reverse the bytes in a 32-bit value */ +#define REVERSE(q) \ + ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate (z_streamp strm, int flush) +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code thisx; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = (char *)"incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (len > state->wbits) { + strm->msg = (char *)"invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = (char *)"unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = (char *)"header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = REVERSE(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.val < 16) { + NEEDBITS(thisx.bits); + DROPBITS(thisx.bits); + state->lens[state->have++] = thisx.val; + } + else { + if (thisx.val == 16) { + NEEDBITS(thisx.bits + 2); + DROPBITS(thisx.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (thisx.val == 17) { + NEEDBITS(thisx.bits + 3); + DROPBITS(thisx.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(thisx.bits + 7); + DROPBITS(thisx.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + break; + } + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.op && (thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + state->length = (unsigned)thisx.val; + if ((int)(thisx.op) == 0) { + Tracevv((stderr, thisx.val >= 0x20 && thisx.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", thisx.val)); + state->mode = LIT; + break; + } + if (thisx.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + if (thisx.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(thisx.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->mode = DIST; + case DIST: + for (;;) { + thisx = state->distcode[BITS(state->distbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if ((thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + if (thisx.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)thisx.val; + state->extra = (unsigned)(thisx.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + if (state->offset > state->whave + out - left) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->write) { + copy -= state->write; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->write - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + REVERSE(hold)) != state->check) { + strm->msg = (char *)"incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = (char *)"incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) + if (updatewindow(strm, out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd (z_streamp strm) +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength) +{ + struct inflate_state FAR *state; + unsigned long id_; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary id */ + if (state->mode == DICT) { + id_ = adler32(0L, Z_NULL, 0); + id_ = adler32(id_, dictionary, dictLength); + if (id_ != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window */ + if (updatewindow(strm, strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + if (dictLength > state->wsize) { + zmemcpy(state->window, dictionary + dictLength - state->wsize, + state->wsize); + state->whave = state->wsize; + } + else { + zmemcpy(state->window + state->wsize - dictLength, dictionary, + dictLength); + state->whave = dictLength; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader (z_streamp strm, gz_headerp head) +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch (unsigned FAR *have, unsigned char FAR *buf, unsigned len) +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync (z_streamp strm) +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint (z_streamp strm) +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(z_streamp dest, z_streamp source) +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy(copy, state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} diff --git a/modules/beast_core/zip/zlib/inflate.h b/modules/beast_core/zip/zlib/inflate.h new file mode 100644 index 0000000000..721caa6efc --- /dev/null +++ b/modules/beast_core/zip/zlib/inflate.h @@ -0,0 +1,121 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFLATE_H_ +#define _INFLATE_H_ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN, /* i: waiting for length/lit code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD or MEM mode -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME + NAME -> COMMENT -> HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or CHECK + STORED -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; + + +#endif diff --git a/modules/beast_core/zip/zlib/inftrees.c b/modules/beast_core/zip/zlib/inftrees.c new file mode 100644 index 0000000000..7afbb3888d --- /dev/null +++ b/modules/beast_core/zip/zlib/inftrees.c @@ -0,0 +1,328 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.3 Copyright 1995-2005 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table (codetype type, + unsigned short FAR *lens, + unsigned codes, + code FAR * FAR *table, + unsigned FAR *bits, + unsigned short FAR *work) +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code thisx; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 201, 196}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + thisx.op = (unsigned char)64; /* invalid code marker */ + thisx.bits = (unsigned char)1; + thisx.val = (unsigned short)0; + *(*table)++ = thisx; /* make a table to force an error */ + *(*table)++ = thisx; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked when a LENS table is being made + against the space in *table, ENOUGH, minus the maximum space needed by + the worst case distance code, MAXD. This should never happen, but the + sufficiency of ENOUGH has not been proven exhaustively, hence the check. + This assumes that when type == LENS, bits == 9. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + thisx.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + thisx.op = (unsigned char)0; + thisx.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + thisx.op = (unsigned char)(extra[work[sym]]); + thisx.val = base[work[sym]]; + } + else { + thisx.op = (unsigned char)(32 + 64); /* end of block */ + thisx.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = thisx; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + thisx.op = (unsigned char)64; /* invalid code marker */ + thisx.bits = (unsigned char)(len - drop); + thisx.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + thisx.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = thisx; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/modules/beast_core/zip/zlib/inftrees.h b/modules/beast_core/zip/zlib/inftrees.h new file mode 100644 index 0000000000..97b5ab3e4f --- /dev/null +++ b/modules/beast_core/zip/zlib/inftrees.h @@ -0,0 +1,61 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFTREES_H_ +#define _INFTREES_H_ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1444 code structures (852 for length/literals + and 592 for distances, the latter actually the result of an + exhaustive search). The true maximum is not known, but the value + below is more than safe. */ +#define ENOUGH 2048 +#define MAXD 592 + +/* Type of code to build for inftable() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); + + +#endif diff --git a/modules/beast_core/zip/zlib/trees.c b/modules/beast_core/zip/zlib/trees.c new file mode 100644 index 0000000000..d5bd6c965a --- /dev/null +++ b/modules/beast_core/zip/zlib/trees.c @@ -0,0 +1,1191 @@ +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1995-2005 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in a compressed form which is itself + * a Huffman encoding of the lengths of all the code strings (in + * ascending order by source values). The actual code strings are + * reconstructed from the lengths in the inflate process, as described + * in the deflate specification. + * + * REFERENCES + * + * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". + * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + */ + +/* @(#) $Id: trees.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +/* #define GEN_TREES_H */ + +#include "deflate.h" + +#ifdef DEBUG +# include +#endif + +/* =========================================================================== + * Constants + */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */ + = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; + +local const int extra_dbits[D_CODES] /* extra bits for each distance code */ + = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */ + = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; + +local const uch bl_order[BL_CODES] + = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +#define Buf_size (8 * 2*sizeof(char)) +/* Number of bits used within bi_buf. (bi_buf might be implemented on + * more than 16 bits on some systems.) + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +#define DIST_CODE_LEN 512 /* see definition of array dist_code below */ + +#if defined(GEN_TREES_H) || !defined(STDC) +/* non ANSI compilers may not accept trees.h */ + +local ct_data static_ltree[L_CODES+2]; +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +local ct_data static_dtree[D_CODES]; +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +uch _dist_code[DIST_CODE_LEN]; +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +uch _length_code[MAX_MATCH-MIN_MATCH+1]; +/* length code for each normalized match length (0 == MIN_MATCH) */ + +local int base_length[LENGTH_CODES]; +/* First normalized length for each code (0 = MIN_MATCH) */ + +local int base_dist[D_CODES]; +/* First normalized distance for each code (0 = distance of 1) */ + +#else +# include "trees.h" +#endif /* GEN_TREES_H */ + +struct static_tree_desc_s { + const ct_data *static_tree; /* static tree or NULL */ + const intf *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ +}; + +local static_tree_desc static_l_desc = +{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; + +local static_tree_desc static_d_desc = +{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; + +local static_tree_desc static_bl_desc = +{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; + +/* =========================================================================== + * Local (static) routines in this file. + */ + +local void tr_static_init OF((void)); +local void init_block OF((deflate_state *s)); +local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +local void build_tree OF((deflate_state *s, tree_desc *desc)); +local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local int build_bl_tree OF((deflate_state *s)); +local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, + int blcodes)); +local void compress_block OF((deflate_state *s, ct_data *ltree, + ct_data *dtree)); +local void set_data_type OF((deflate_state *s)); +local unsigned bi_reverse OF((unsigned value, int length)); +local void bi_windup OF((deflate_state *s)); +local void bi_flush OF((deflate_state *s)); +local void copy_block OF((deflate_state *s, charf *buf, unsigned len, + int header)); + +#ifdef GEN_TREES_H +local void gen_trees_header OF((void)); +#endif + +#ifndef DEBUG +# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(s, c, tree) \ + { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \ + send_bits(s, tree[c].Code, tree[c].Len); } +#endif + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +#ifdef DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits (deflate_state *s, int value, int length) +{ + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (value << s->bi_valid); + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += length - Buf_size; + } else { + s->bi_buf |= value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = value;\ + s->bi_buf |= (val << s->bi_valid);\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ +} +#endif /* DEBUG */ + + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Initialize the various 'constant' tables. + */ +local void tr_static_init() +{ +#if defined(GEN_TREES_H) || !defined(STDC) + static int static_init_done = 0; + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = (uch)code; + } + } + Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; + n = 0; + while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse((unsigned)n, 5); + } + static_init_done = 1; + +# ifdef GEN_TREES_H + gen_trees_header(); +# endif +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ +} + +/* =========================================================================== + * Genererate the file trees.h describing the static trees. + */ +#ifdef GEN_TREES_H +# ifndef DEBUG +# include +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, "const uch _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +void _tr_init(deflate_state *s) +{ + tr_static_init(); + + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; + s->last_eob_len = 8; /* enough lookahead for inflate */ +#ifdef DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif + + /* Initialize the first block of the first file: */ + init_block(s); +} + +/* =========================================================================== + * Initialize a new block. + */ +local void init_block (deflate_state *s) +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->last_lit = s->matches = 0; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(s, tree, top) \ +{\ + top = s->heap[SMALLEST]; \ + s->heap[SMALLEST] = s->heap[s->heap_len--]; \ + pqdownheap(s, tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m, depth) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +local void pqdownheap (deflate_state *s, + ct_data *tree, /* the tree to restore */ + int k) /* node to move down */ +{ + int v = s->heap[k]; + int j = k << 1; /* left son of k */ + while (j <= s->heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s->heap_len && + smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s->heap[j], s->depth)) break; + + /* Exchange v with the smallest son */ + s->heap[k] = s->heap[j]; k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s->heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +local void gen_bitlen (deflate_state *s, tree_desc *desc) +{ + ct_data *tree = desc->dyn_tree; + int max_code = desc->max_code; + const ct_data *stree = desc->stat_desc->static_tree; + const intf *extra = desc->stat_desc->extra_bits; + int base = desc->stat_desc->extra_base; + int max_length = desc->stat_desc->max_length; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ + + for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + n = s->heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) bits = max_length, overflow++; + tree[n].Len = (ush)bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s->bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n].Freq; + s->opt_len += (ulg)f * (bits + xbits); + if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits); + } + if (overflow == 0) return; + + Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s->bl_count[bits] == 0) bits--; + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = s->bl_count[bits]; + while (n != 0) { + m = s->heap[--h]; + if (m > max_code) continue; + if ((unsigned) tree[m].Len != (unsigned) bits) { + Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s->opt_len += ((long)bits - (long)tree[m].Len) + *(long)tree[m].Freq; + tree[m].Len = (ush)bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +local void gen_codes (ct_data *tree, /* the tree to decorate */ + int max_code, /* largest code with non zero frequency */ + ushf *bl_count) /* number of codes at each bit length */ +{ + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits-1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; + const ct_data *stree = desc->stat_desc->static_tree; + int elems = desc->stat_desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s->heap_len = 0, s->heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + s->heap[++(s->heap_len)] = max_code = n; + s->depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s->heap_len < 2) { + node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0); + tree[node].Freq = 1; + s->depth[node] = 0; + s->opt_len--; if (stree) s->static_len -= stree[node].Len; + /* node is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + pqremove(s, tree, n); /* n = node of least frequency */ + m = s->heap[SMALLEST]; /* m = node of next least frequency */ + + s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */ + s->heap[--(s->heap_max)] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ? + s->depth[n] : s->depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + + } while (s->heap_len >= 2); + + s->heap[--(s->heap_max)] = s->heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, (tree_desc *)desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes ((ct_data *)tree, max_code, s->bl_count); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +local void scan_tree (deflate_state *s, + ct_data *tree, /* the tree to be scanned */ + int max_code) /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) max_count = 138, min_count = 3; + tree[max_code+1].Len = (ush)0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + s->bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) s->bl_tree[curlen].Freq++; + s->bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + s->bl_tree[REPZ_3_10].Freq++; + } else { + s->bl_tree[REPZ_11_138].Freq++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +local void send_tree (deflate_state *s, + ct_data *tree, /* the tree to be scanned */ + int max_code) /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen == 0) max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { send_code(s, curlen, s->bl_tree); } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(s, curlen, s->bl_tree); count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +local int build_bl_tree (deflate_state *s) +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, (tree_desc *)(&(s->bl_desc))); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; + } + /* Update opt_len to include the bit length tree and counts */ + s->opt_len += 3*(max_blindex+1) + 5+5+4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +local void send_all_trees (deflate_state *s, + int lcodes, int dcodes, int blcodes) /* number of codes for each tree */ +{ + int rank; /* index in bl_order */ + + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + +/* =========================================================================== + * Send a stored block + */ +void _tr_stored_block (deflate_state *s, charf *buf, ulg stored_len, int eof) +{ + send_bits(s, (STORED_BLOCK<<1)+eof, 3); /* send block type */ +#ifdef DEBUG + s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; + s->compressed_len += (stored_len + 4) << 3; +#endif + copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + * The current inflate code requires 9 bits of lookahead. If the + * last two codes for the previous block (real code plus EOB) were coded + * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + * the last real code. In this case we send two empty static blocks instead + * of one. (There are no problems if the previous block is stored or fixed.) + * To simplify the code, we assume the worst case of last real code encoded + * on one bit only. + */ +void _tr_align (deflate_state *s) +{ + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif + bi_flush(s); + /* Of the 10 bits for the empty block, we have already sent + * (10 - bi_valid) bits. The lookahead for the last real code (before + * the EOB of the previous block) was thus at least one plus the length + * of the EOB plus what we have just sent of the empty static block. + */ + if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; +#endif + bi_flush(s); + } + s->last_eob_len = 7; +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +void _tr_flush_block (deflate_state *s, + charf *buf, /* input block, or NULL if too old */ + ulg stored_len, /* length of input block */ + int eof) /* true if this is the last block for a file */ +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s->level > 0) { + + /* Check if the file is binary or text */ + if (stored_len > 0 && s->strm->data_type == Z_UNKNOWN) + set_data_type(s); + + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; + + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + + } else { + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else + if (stored_len+4 <= opt_lenb && buf != (char*)0) { + /* 4: two words for the lengths */ +#endif + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, eof); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+eof, 3); + compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->static_len; +#endif + } else { + send_bits(s, (DYN_TREES<<1)+eof, 3); + send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, + max_blindex+1); + compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->opt_len; +#endif + } + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (eof) { + bi_windup(s); +#ifdef DEBUG + s->compressed_len += 7; /* align on byte boundary */ +#endif + } + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*eof)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int _tr_tally (deflate_state *s, + unsigned dist, /* distance of matched string */ + unsigned lc) /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block (deflate_state *s, + ct_data *ltree, /* literal tree */ + ct_data *dtree) /* distance tree */ +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->last_lit != 0) do { + dist = s->d_buf[lx]; + lc = s->l_buf[lx++]; + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + "pendingBuf overflow"); + + } while (lx < s->last_lit); + + send_code(s, END_BLOCK, ltree); + s->last_eob_len = ltree[END_BLOCK].Len; +} + +/* =========================================================================== + * Set the data type to BINARY or TEXT, using a crude approximation: + * set it to Z_TEXT if all symbols are either printable characters (33 to 255) + * or white spaces (9 to 13, or 32); or set it to Z_BINARY otherwise. + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local void set_data_type (deflate_state *s) +{ + int n; + + for (n = 0; n < 9; n++) + if (s->dyn_ltree[n].Freq != 0) + break; + if (n == 9) + for (n = 14; n < 32; n++) + if (s->dyn_ltree[n].Freq != 0) + break; + s->strm->data_type = (n == 32) ? Z_TEXT : Z_BINARY; +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse (unsigned code, int len) +{ + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush (deflate_state *s) +{ + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup (deflate_state *s) +{ + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +local void copy_block(deflate_state *s, + charf *buf, /* the input data */ + unsigned len, /* its length */ + int header) /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + s->last_eob_len = 8; /* enough lookahead for inflate */ + + if (header) { + put_short(s, (ush)len); + put_short(s, (ush)~len); +#ifdef DEBUG + s->bits_sent += 2*16; +#endif + } +#ifdef DEBUG + s->bits_sent += (ulg)len<<3; +#endif + while (len--) { + put_byte(s, *buf++); + } +} diff --git a/modules/beast_core/zip/zlib/trees.h b/modules/beast_core/zip/zlib/trees.h new file mode 100644 index 0000000000..aadfa16dea --- /dev/null +++ b/modules/beast_core/zip/zlib/trees.h @@ -0,0 +1,127 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; diff --git a/modules/beast_core/zip/zlib/uncompr.c b/modules/beast_core/zip/zlib/uncompr.c new file mode 100644 index 0000000000..032bd0eb54 --- /dev/null +++ b/modules/beast_core/zip/zlib/uncompr.c @@ -0,0 +1,60 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: uncompr.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ +int ZEXPORT uncompress (Bytef *dest, + uLongf *destLen, + const Bytef *source, + uLong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} diff --git a/modules/beast_core/zip/zlib/zconf.h b/modules/beast_core/zip/zlib/zconf.h new file mode 100644 index 0000000000..76298b7838 --- /dev/null +++ b/modules/beast_core/zip/zlib/zconf.h @@ -0,0 +1,345 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zconf.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef ZCONF_H +#define ZCONF_H + +// *** Just a few hacks here to make it compile nicely with Beast.. +#define Z_PREFIX 1 +#undef __MACTYPES__ + +#ifdef _MSC_VER + #pragma warning (disable : 4131 4127 4244 4267) +#endif + + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define inflatePrime z_inflatePrime +# define inflateGetHeader z_inflateGetHeader +# define adler32_combine z_adler32_combine +# define crc32_combine z_crc32_combine +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/modules/beast_core/zip/zlib/zconf.in.h b/modules/beast_core/zip/zlib/zconf.in.h new file mode 100644 index 0000000000..7128a2dc0c --- /dev/null +++ b/modules/beast_core/zip/zlib/zconf.in.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zconf.in.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/modules/beast_core/zip/zlib/zlib.h b/modules/beast_core/zip/zlib/zlib.h new file mode 100644 index 0000000000..9a7307a513 --- /dev/null +++ b/modules/beast_core/zip/zlib/zlib.h @@ -0,0 +1,1358 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +//extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +//ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +//ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +//} +#endif + +#endif /* ZLIB_H */ diff --git a/modules/beast_core/zip/zlib/zutil.c b/modules/beast_core/zip/zlib/zutil.c new file mode 100644 index 0000000000..26f889583a --- /dev/null +++ b/modules/beast_core/zip/zlib/zutil.c @@ -0,0 +1,311 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zutil.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#include "zutil.h" + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +const char * const z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +/*const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch (sizeof(uInt)) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch (sizeof(uLong)) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch (sizeof(voidpf)) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch (sizeof(z_off_t)) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#ifdef STDC +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +}*/ + +#if 0 + +# ifndef verbose +# define verbose 0 +# endif +int z_verbose = verbose; + +void z_error (const char *m) +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(int err) +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ diff --git a/modules/beast_core/zip/zlib/zutil.h b/modules/beast_core/zip/zlib/zutil.h new file mode 100644 index 0000000000..6ec0552fd1 --- /dev/null +++ b/modules/beast_core/zip/zlib/zutil.h @@ -0,0 +1,271 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id: zutil.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#define ZLIB_INTERNAL +#include "zlib.h" + +#ifdef STDC +# ifndef _WIN32_WCE +# include +# endif +# include +# include +#endif +#ifdef NO_ERRNO_H +# ifdef _WIN32_WCE + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. We rename it to + * avoid conflict with other libraries that use the same workaround. + */ +# define errno z_errno +# endif + extern int errno; +#else +# ifndef _WIN32_WCE +# include +# endif +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# ifdef M_I86 + #include +# endif +#endif + +#if defined(MACOS) || TARGET_OS_MAC +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS + /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 + /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# define vsnprintf _vsnprintf +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +#endif +#ifdef VMS +# define NO_vsnprintf +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#if 0 +# include + extern int z_verbose; + extern void z_error OF((const char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +# define z_error(x) +# define z_verbose 0 +#endif + + +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* ZUTIL_H */