From 585aff42fae1399dbc2cbecb417fd97427e0e794 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 31 Jul 2013 20:05:04 +0100 Subject: [PATCH] Add juce_core module code --- source/modules/JuceHeader.h | 35 + source/modules/juce_core/AppConfig.h | 98 + source/modules/juce_core/Makefile | 88 + .../containers/juce_AbstractFifo.cpp | 234 ++ .../juce_core/containers/juce_AbstractFifo.h | 222 ++ .../modules/juce_core/containers/juce_Array.h | 1064 ++++++++ .../containers/juce_ArrayAllocationBase.h | 140 + .../containers/juce_DynamicObject.cpp | 79 + .../juce_core/containers/juce_DynamicObject.h | 126 + .../containers/juce_ElementComparator.h | 279 ++ .../juce_core/containers/juce_HashMap.h | 460 ++++ .../containers/juce_LinkedListPointer.h | 371 +++ .../containers/juce_NamedValueSet.cpp | 309 +++ .../juce_core/containers/juce_NamedValueSet.h | 169 ++ .../juce_core/containers/juce_OwnedArray.h | 897 ++++++ .../juce_core/containers/juce_PropertySet.cpp | 223 ++ .../juce_core/containers/juce_PropertySet.h | 219 ++ .../containers/juce_ReferenceCountedArray.h | 872 ++++++ .../containers/juce_ScopedValueSetter.h | 100 + .../juce_core/containers/juce_SortedSet.h | 497 ++++ .../juce_core/containers/juce_SparseSet.h | 301 +++ .../juce_core/containers/juce_Variant.cpp | 709 +++++ .../juce_core/containers/juce_Variant.h | 307 +++ .../files/juce_DirectoryIterator.cpp | 159 ++ .../juce_core/files/juce_DirectoryIterator.h | 162 ++ source/modules/juce_core/files/juce_File.cpp | 1091 ++++++++ source/modules/juce_core/files/juce_File.h | 959 +++++++ .../juce_core/files/juce_FileInputStream.cpp | 95 + .../juce_core/files/juce_FileInputStream.h | 99 + .../juce_core/files/juce_FileOutputStream.cpp | 135 + .../juce_core/files/juce_FileOutputStream.h | 119 + .../juce_core/files/juce_FileSearchPath.cpp | 171 ++ .../juce_core/files/juce_FileSearchPath.h | 169 ++ .../juce_core/files/juce_MemoryMappedFile.h | 116 + .../juce_core/files/juce_TemporaryFile.cpp | 117 + .../juce_core/files/juce_TemporaryFile.h | 171 ++ source/modules/juce_core/json/juce_JSON.cpp | 650 +++++ source/modules/juce_core/json/juce_JSON.h | 118 + source/modules/juce_core/juce_core.cpp | 218 ++ source/modules/juce_core/juce_core.h | 253 ++ source/modules/juce_core/juce_core.mm | 29 + source/modules/juce_core/juce_module_info | 38 + .../juce_core/logging/juce_FileLogger.cpp | 134 + .../juce_core/logging/juce_FileLogger.h | 139 + .../modules/juce_core/logging/juce_Logger.cpp | 63 + .../modules/juce_core/logging/juce_Logger.h | 99 + .../juce_core/maths/juce_BigInteger.cpp | 1021 +++++++ .../modules/juce_core/maths/juce_BigInteger.h | 334 +++ .../juce_core/maths/juce_Expression.cpp | 1185 ++++++++ .../modules/juce_core/maths/juce_Expression.h | 274 ++ .../juce_core/maths/juce_MathsFunctions.h | 514 ++++ .../modules/juce_core/maths/juce_Random.cpp | 190 ++ source/modules/juce_core/maths/juce_Random.h | 143 + source/modules/juce_core/maths/juce_Range.h | 264 ++ source/modules/juce_core/memory/juce_Atomic.h | 396 +++ .../modules/juce_core/memory/juce_ByteOrder.h | 186 ++ .../modules/juce_core/memory/juce_HeapBlock.h | 308 +++ .../memory/juce_LeakedObjectDetector.h | 149 + source/modules/juce_core/memory/juce_Memory.h | 126 + .../juce_core/memory/juce_MemoryBlock.cpp | 421 +++ .../juce_core/memory/juce_MemoryBlock.h | 260 ++ .../memory/juce_OptionalScopedPointer.h | 188 ++ .../memory/juce_ReferenceCountedObject.h | 400 +++ .../juce_core/memory/juce_ScopedPointer.h | 253 ++ .../modules/juce_core/memory/juce_Singleton.h | 292 ++ .../juce_core/memory/juce_WeakReference.h | 214 ++ source/modules/juce_core/misc/juce_Result.cpp | 83 + source/modules/juce_core/misc/juce_Result.h | 127 + source/modules/juce_core/misc/juce_Uuid.cpp | 114 + source/modules/juce_core/misc/juce_Uuid.h | 114 + .../juce_core/misc/juce_WindowsRegistry.h | 130 + .../native/java/JuceAppActivity.java | 707 +++++ .../native/juce_BasicNativeHeaders.h | 223 ++ .../juce_core/native/juce_android_Files.cpp | 241 ++ .../native/juce_android_JNIHelpers.h | 407 +++ .../juce_core/native/juce_android_Misc.cpp | 32 + .../juce_core/native/juce_android_Network.cpp | 175 ++ .../native/juce_android_SystemStats.cpp | 307 +++ .../juce_core/native/juce_android_Threads.cpp | 81 + .../juce_core/native/juce_linux_Files.cpp | 378 +++ .../juce_core/native/juce_linux_Network.cpp | 462 ++++ .../native/juce_linux_SystemStats.cpp | 181 ++ .../juce_core/native/juce_linux_Threads.cpp | 94 + .../juce_core/native/juce_mac_Files.mm | 485 ++++ .../juce_core/native/juce_mac_Network.mm | 432 +++ .../juce_core/native/juce_mac_Strings.mm | 96 + .../juce_core/native/juce_mac_SystemStats.mm | 292 ++ .../juce_core/native/juce_mac_Threads.mm | 98 + .../juce_core/native/juce_osx_ObjCHelpers.h | 167 ++ .../juce_core/native/juce_posix_NamedPipe.cpp | 219 ++ .../juce_core/native/juce_posix_SharedCode.h | 1275 +++++++++ .../juce_core/native/juce_win32_ComSmartPtr.h | 170 ++ .../juce_core/native/juce_win32_Files.cpp | 961 +++++++ .../juce_core/native/juce_win32_Network.cpp | 469 ++++ .../juce_core/native/juce_win32_Registry.cpp | 236 ++ .../native/juce_win32_SystemStats.cpp | 435 +++ .../juce_core/native/juce_win32_Threads.cpp | 639 +++++ .../juce_core/network/juce_IPAddress.cpp | 149 + .../juce_core/network/juce_IPAddress.h | 82 + .../juce_core/network/juce_MACAddress.cpp | 78 + .../juce_core/network/juce_MACAddress.h | 89 + .../juce_core/network/juce_NamedPipe.cpp | 66 + .../juce_core/network/juce_NamedPipe.h | 105 + .../modules/juce_core/network/juce_Socket.cpp | 593 ++++ .../modules/juce_core/network/juce_Socket.h | 307 +++ source/modules/juce_core/network/juce_URL.cpp | 473 ++++ source/modules/juce_core/network/juce_URL.h | 350 +++ .../streams/juce_BufferedInputStream.cpp | 198 ++ .../streams/juce_BufferedInputStream.h | 96 + .../streams/juce_FileInputSource.cpp | 56 + .../juce_core/streams/juce_FileInputSource.h | 69 + .../juce_core/streams/juce_InputSource.h | 79 + .../juce_core/streams/juce_InputStream.cpp | 236 ++ .../juce_core/streams/juce_InputStream.h | 269 ++ .../streams/juce_MemoryInputStream.cpp | 160 ++ .../streams/juce_MemoryInputStream.h | 100 + .../streams/juce_MemoryOutputStream.cpp | 214 ++ .../streams/juce_MemoryOutputStream.h | 143 + .../juce_core/streams/juce_OutputStream.cpp | 336 +++ .../juce_core/streams/juce_OutputStream.h | 284 ++ .../streams/juce_SubregionStream.cpp | 82 + .../juce_core/streams/juce_SubregionStream.h | 91 + .../juce_core/system/juce_PlatformDefs.h | 373 +++ .../juce_core/system/juce_StandardHeader.h | 171 ++ .../juce_core/system/juce_SystemStats.cpp | 183 ++ .../juce_core/system/juce_SystemStats.h | 188 ++ .../juce_core/system/juce_TargetPlatform.h | 205 ++ .../juce_core/text/juce_CharPointer_ASCII.h | 387 +++ .../juce_core/text/juce_CharPointer_UTF16.h | 501 ++++ .../juce_core/text/juce_CharPointer_UTF32.h | 378 +++ .../juce_core/text/juce_CharPointer_UTF8.h | 565 ++++ .../text/juce_CharacterFunctions.cpp | 154 ++ .../juce_core/text/juce_CharacterFunctions.h | 587 ++++ .../juce_core/text/juce_Identifier.cpp | 74 + .../modules/juce_core/text/juce_Identifier.h | 111 + .../juce_core/text/juce_LocalisedStrings.cpp | 187 ++ .../juce_core/text/juce_LocalisedStrings.h | 224 ++ source/modules/juce_core/text/juce_NewLine.h | 78 + source/modules/juce_core/text/juce_String.cpp | 2396 +++++++++++++++++ source/modules/juce_core/text/juce_String.h | 1351 ++++++++++ .../juce_core/text/juce_StringArray.cpp | 520 ++++ .../modules/juce_core/text/juce_StringArray.h | 422 +++ .../juce_core/text/juce_StringPairArray.cpp | 142 + .../juce_core/text/juce_StringPairArray.h | 163 ++ .../juce_core/text/juce_StringPool.cpp | 114 + .../modules/juce_core/text/juce_StringPool.h | 95 + .../modules/juce_core/text/juce_TextDiff.cpp | 246 ++ source/modules/juce_core/text/juce_TextDiff.h | 80 + .../juce_core/threads/juce_ChildProcess.cpp | 93 + .../juce_core/threads/juce_ChildProcess.h | 105 + .../juce_core/threads/juce_CriticalSection.h | 257 ++ .../juce_core/threads/juce_DynamicLibrary.h | 85 + .../threads/juce_HighResolutionTimer.cpp | 36 + .../threads/juce_HighResolutionTimer.h | 109 + .../juce_core/threads/juce_InterProcessLock.h | 131 + .../modules/juce_core/threads/juce_Process.h | 155 ++ .../juce_core/threads/juce_ReadWriteLock.cpp | 158 ++ .../juce_core/threads/juce_ReadWriteLock.h | 159 ++ .../juce_core/threads/juce_ScopedLock.h | 237 ++ .../juce_core/threads/juce_ScopedReadLock.h | 92 + .../juce_core/threads/juce_ScopedWriteLock.h | 92 + .../modules/juce_core/threads/juce_SpinLock.h | 93 + .../modules/juce_core/threads/juce_Thread.cpp | 363 +++ .../modules/juce_core/threads/juce_Thread.h | 292 ++ .../juce_core/threads/juce_ThreadLocalValue.h | 199 ++ .../juce_core/threads/juce_ThreadPool.cpp | 380 +++ .../juce_core/threads/juce_ThreadPool.h | 318 +++ .../threads/juce_TimeSliceThread.cpp | 171 ++ .../juce_core/threads/juce_TimeSliceThread.h | 152 ++ .../juce_core/threads/juce_WaitableEvent.h | 121 + .../time/juce_PerformanceCounter.cpp | 96 + .../juce_core/time/juce_PerformanceCounter.h | 108 + .../juce_core/time/juce_RelativeTime.cpp | 138 + .../juce_core/time/juce_RelativeTime.h | 181 ++ source/modules/juce_core/time/juce_Time.cpp | 446 +++ source/modules/juce_core/time/juce_Time.h | 406 +++ .../juce_core/unit_tests/juce_UnitTest.cpp | 237 ++ .../juce_core/unit_tests/juce_UnitTest.h | 290 ++ .../juce_core/xml/juce_XmlDocument.cpp | 859 ++++++ .../modules/juce_core/xml/juce_XmlDocument.h | 186 ++ .../modules/juce_core/xml/juce_XmlElement.cpp | 829 ++++++ .../modules/juce_core/xml/juce_XmlElement.h | 739 +++++ .../zip/juce_GZIPCompressorOutputStream.cpp | 213 ++ .../zip/juce_GZIPCompressorOutputStream.h | 105 + .../zip/juce_GZIPDecompressorInputStream.cpp | 290 ++ .../zip/juce_GZIPDecompressorInputStream.h | 101 + source/modules/juce_core/zip/juce_ZipFile.cpp | 598 ++++ source/modules/juce_core/zip/juce_ZipFile.h | 249 ++ source/modules/juce_core/zip/zlib/README | 125 + source/modules/juce_core/zip/zlib/adler32.c | 143 + source/modules/juce_core/zip/zlib/compress.c | 70 + source/modules/juce_core/zip/zlib/crc32.c | 407 +++ source/modules/juce_core/zip/zlib/crc32.h | 441 +++ source/modules/juce_core/zip/zlib/deflate.c | 1679 ++++++++++++ source/modules/juce_core/zip/zlib/deflate.h | 333 +++ source/modules/juce_core/zip/zlib/infback.c | 611 +++++ source/modules/juce_core/zip/zlib/inffast.c | 316 +++ source/modules/juce_core/zip/zlib/inffast.h | 11 + source/modules/juce_core/zip/zlib/inffixed.h | 94 + source/modules/juce_core/zip/zlib/inflate.c | 1339 +++++++++ source/modules/juce_core/zip/zlib/inflate.h | 121 + source/modules/juce_core/zip/zlib/inftrees.c | 328 +++ source/modules/juce_core/zip/zlib/inftrees.h | 61 + source/modules/juce_core/zip/zlib/trees.c | 1191 ++++++++ source/modules/juce_core/zip/zlib/trees.h | 127 + source/modules/juce_core/zip/zlib/uncompr.c | 60 + source/modules/juce_core/zip/zlib/zconf.h | 345 +++ source/modules/juce_core/zip/zlib/zconf.in.h | 332 +++ source/modules/juce_core/zip/zlib/zlib.h | 1358 ++++++++++ source/modules/juce_core/zip/zlib/zutil.c | 311 +++ source/modules/juce_core/zip/zlib/zutil.h | 271 ++ 211 files changed, 64594 insertions(+) create mode 100755 source/modules/JuceHeader.h create mode 100755 source/modules/juce_core/AppConfig.h create mode 100644 source/modules/juce_core/Makefile create mode 100644 source/modules/juce_core/containers/juce_AbstractFifo.cpp create mode 100644 source/modules/juce_core/containers/juce_AbstractFifo.h create mode 100644 source/modules/juce_core/containers/juce_Array.h create mode 100644 source/modules/juce_core/containers/juce_ArrayAllocationBase.h create mode 100644 source/modules/juce_core/containers/juce_DynamicObject.cpp create mode 100644 source/modules/juce_core/containers/juce_DynamicObject.h create mode 100644 source/modules/juce_core/containers/juce_ElementComparator.h create mode 100644 source/modules/juce_core/containers/juce_HashMap.h create mode 100644 source/modules/juce_core/containers/juce_LinkedListPointer.h create mode 100644 source/modules/juce_core/containers/juce_NamedValueSet.cpp create mode 100644 source/modules/juce_core/containers/juce_NamedValueSet.h create mode 100644 source/modules/juce_core/containers/juce_OwnedArray.h create mode 100644 source/modules/juce_core/containers/juce_PropertySet.cpp create mode 100644 source/modules/juce_core/containers/juce_PropertySet.h create mode 100644 source/modules/juce_core/containers/juce_ReferenceCountedArray.h create mode 100644 source/modules/juce_core/containers/juce_ScopedValueSetter.h create mode 100644 source/modules/juce_core/containers/juce_SortedSet.h create mode 100644 source/modules/juce_core/containers/juce_SparseSet.h create mode 100644 source/modules/juce_core/containers/juce_Variant.cpp create mode 100644 source/modules/juce_core/containers/juce_Variant.h create mode 100644 source/modules/juce_core/files/juce_DirectoryIterator.cpp create mode 100644 source/modules/juce_core/files/juce_DirectoryIterator.h create mode 100644 source/modules/juce_core/files/juce_File.cpp create mode 100644 source/modules/juce_core/files/juce_File.h create mode 100644 source/modules/juce_core/files/juce_FileInputStream.cpp create mode 100644 source/modules/juce_core/files/juce_FileInputStream.h create mode 100644 source/modules/juce_core/files/juce_FileOutputStream.cpp create mode 100644 source/modules/juce_core/files/juce_FileOutputStream.h create mode 100644 source/modules/juce_core/files/juce_FileSearchPath.cpp create mode 100644 source/modules/juce_core/files/juce_FileSearchPath.h create mode 100644 source/modules/juce_core/files/juce_MemoryMappedFile.h create mode 100644 source/modules/juce_core/files/juce_TemporaryFile.cpp create mode 100644 source/modules/juce_core/files/juce_TemporaryFile.h create mode 100644 source/modules/juce_core/json/juce_JSON.cpp create mode 100644 source/modules/juce_core/json/juce_JSON.h create mode 100644 source/modules/juce_core/juce_core.cpp create mode 100644 source/modules/juce_core/juce_core.h create mode 100644 source/modules/juce_core/juce_core.mm create mode 100644 source/modules/juce_core/juce_module_info create mode 100644 source/modules/juce_core/logging/juce_FileLogger.cpp create mode 100644 source/modules/juce_core/logging/juce_FileLogger.h create mode 100644 source/modules/juce_core/logging/juce_Logger.cpp create mode 100644 source/modules/juce_core/logging/juce_Logger.h create mode 100644 source/modules/juce_core/maths/juce_BigInteger.cpp create mode 100644 source/modules/juce_core/maths/juce_BigInteger.h create mode 100644 source/modules/juce_core/maths/juce_Expression.cpp create mode 100644 source/modules/juce_core/maths/juce_Expression.h create mode 100644 source/modules/juce_core/maths/juce_MathsFunctions.h create mode 100644 source/modules/juce_core/maths/juce_Random.cpp create mode 100644 source/modules/juce_core/maths/juce_Random.h create mode 100644 source/modules/juce_core/maths/juce_Range.h create mode 100644 source/modules/juce_core/memory/juce_Atomic.h create mode 100644 source/modules/juce_core/memory/juce_ByteOrder.h create mode 100644 source/modules/juce_core/memory/juce_HeapBlock.h create mode 100644 source/modules/juce_core/memory/juce_LeakedObjectDetector.h create mode 100644 source/modules/juce_core/memory/juce_Memory.h create mode 100644 source/modules/juce_core/memory/juce_MemoryBlock.cpp create mode 100644 source/modules/juce_core/memory/juce_MemoryBlock.h create mode 100644 source/modules/juce_core/memory/juce_OptionalScopedPointer.h create mode 100644 source/modules/juce_core/memory/juce_ReferenceCountedObject.h create mode 100644 source/modules/juce_core/memory/juce_ScopedPointer.h create mode 100644 source/modules/juce_core/memory/juce_Singleton.h create mode 100644 source/modules/juce_core/memory/juce_WeakReference.h create mode 100644 source/modules/juce_core/misc/juce_Result.cpp create mode 100644 source/modules/juce_core/misc/juce_Result.h create mode 100644 source/modules/juce_core/misc/juce_Uuid.cpp create mode 100644 source/modules/juce_core/misc/juce_Uuid.h create mode 100644 source/modules/juce_core/misc/juce_WindowsRegistry.h create mode 100644 source/modules/juce_core/native/java/JuceAppActivity.java create mode 100644 source/modules/juce_core/native/juce_BasicNativeHeaders.h create mode 100644 source/modules/juce_core/native/juce_android_Files.cpp create mode 100644 source/modules/juce_core/native/juce_android_JNIHelpers.h create mode 100644 source/modules/juce_core/native/juce_android_Misc.cpp create mode 100644 source/modules/juce_core/native/juce_android_Network.cpp create mode 100644 source/modules/juce_core/native/juce_android_SystemStats.cpp create mode 100644 source/modules/juce_core/native/juce_android_Threads.cpp create mode 100644 source/modules/juce_core/native/juce_linux_Files.cpp create mode 100644 source/modules/juce_core/native/juce_linux_Network.cpp create mode 100644 source/modules/juce_core/native/juce_linux_SystemStats.cpp create mode 100644 source/modules/juce_core/native/juce_linux_Threads.cpp create mode 100644 source/modules/juce_core/native/juce_mac_Files.mm create mode 100644 source/modules/juce_core/native/juce_mac_Network.mm create mode 100644 source/modules/juce_core/native/juce_mac_Strings.mm create mode 100644 source/modules/juce_core/native/juce_mac_SystemStats.mm create mode 100644 source/modules/juce_core/native/juce_mac_Threads.mm create mode 100644 source/modules/juce_core/native/juce_osx_ObjCHelpers.h create mode 100644 source/modules/juce_core/native/juce_posix_NamedPipe.cpp create mode 100644 source/modules/juce_core/native/juce_posix_SharedCode.h create mode 100644 source/modules/juce_core/native/juce_win32_ComSmartPtr.h create mode 100644 source/modules/juce_core/native/juce_win32_Files.cpp create mode 100644 source/modules/juce_core/native/juce_win32_Network.cpp create mode 100644 source/modules/juce_core/native/juce_win32_Registry.cpp create mode 100644 source/modules/juce_core/native/juce_win32_SystemStats.cpp create mode 100644 source/modules/juce_core/native/juce_win32_Threads.cpp create mode 100644 source/modules/juce_core/network/juce_IPAddress.cpp create mode 100644 source/modules/juce_core/network/juce_IPAddress.h create mode 100644 source/modules/juce_core/network/juce_MACAddress.cpp create mode 100644 source/modules/juce_core/network/juce_MACAddress.h create mode 100644 source/modules/juce_core/network/juce_NamedPipe.cpp create mode 100644 source/modules/juce_core/network/juce_NamedPipe.h create mode 100644 source/modules/juce_core/network/juce_Socket.cpp create mode 100644 source/modules/juce_core/network/juce_Socket.h create mode 100644 source/modules/juce_core/network/juce_URL.cpp create mode 100644 source/modules/juce_core/network/juce_URL.h create mode 100644 source/modules/juce_core/streams/juce_BufferedInputStream.cpp create mode 100644 source/modules/juce_core/streams/juce_BufferedInputStream.h create mode 100644 source/modules/juce_core/streams/juce_FileInputSource.cpp create mode 100644 source/modules/juce_core/streams/juce_FileInputSource.h create mode 100644 source/modules/juce_core/streams/juce_InputSource.h create mode 100644 source/modules/juce_core/streams/juce_InputStream.cpp create mode 100644 source/modules/juce_core/streams/juce_InputStream.h create mode 100644 source/modules/juce_core/streams/juce_MemoryInputStream.cpp create mode 100644 source/modules/juce_core/streams/juce_MemoryInputStream.h create mode 100644 source/modules/juce_core/streams/juce_MemoryOutputStream.cpp create mode 100644 source/modules/juce_core/streams/juce_MemoryOutputStream.h create mode 100644 source/modules/juce_core/streams/juce_OutputStream.cpp create mode 100644 source/modules/juce_core/streams/juce_OutputStream.h create mode 100644 source/modules/juce_core/streams/juce_SubregionStream.cpp create mode 100644 source/modules/juce_core/streams/juce_SubregionStream.h create mode 100644 source/modules/juce_core/system/juce_PlatformDefs.h create mode 100644 source/modules/juce_core/system/juce_StandardHeader.h create mode 100644 source/modules/juce_core/system/juce_SystemStats.cpp create mode 100644 source/modules/juce_core/system/juce_SystemStats.h create mode 100644 source/modules/juce_core/system/juce_TargetPlatform.h create mode 100644 source/modules/juce_core/text/juce_CharPointer_ASCII.h create mode 100644 source/modules/juce_core/text/juce_CharPointer_UTF16.h create mode 100644 source/modules/juce_core/text/juce_CharPointer_UTF32.h create mode 100644 source/modules/juce_core/text/juce_CharPointer_UTF8.h create mode 100644 source/modules/juce_core/text/juce_CharacterFunctions.cpp create mode 100644 source/modules/juce_core/text/juce_CharacterFunctions.h create mode 100644 source/modules/juce_core/text/juce_Identifier.cpp create mode 100644 source/modules/juce_core/text/juce_Identifier.h create mode 100644 source/modules/juce_core/text/juce_LocalisedStrings.cpp create mode 100644 source/modules/juce_core/text/juce_LocalisedStrings.h create mode 100644 source/modules/juce_core/text/juce_NewLine.h create mode 100644 source/modules/juce_core/text/juce_String.cpp create mode 100644 source/modules/juce_core/text/juce_String.h create mode 100644 source/modules/juce_core/text/juce_StringArray.cpp create mode 100644 source/modules/juce_core/text/juce_StringArray.h create mode 100644 source/modules/juce_core/text/juce_StringPairArray.cpp create mode 100644 source/modules/juce_core/text/juce_StringPairArray.h create mode 100644 source/modules/juce_core/text/juce_StringPool.cpp create mode 100644 source/modules/juce_core/text/juce_StringPool.h create mode 100644 source/modules/juce_core/text/juce_TextDiff.cpp create mode 100644 source/modules/juce_core/text/juce_TextDiff.h create mode 100644 source/modules/juce_core/threads/juce_ChildProcess.cpp create mode 100644 source/modules/juce_core/threads/juce_ChildProcess.h create mode 100644 source/modules/juce_core/threads/juce_CriticalSection.h create mode 100644 source/modules/juce_core/threads/juce_DynamicLibrary.h create mode 100644 source/modules/juce_core/threads/juce_HighResolutionTimer.cpp create mode 100644 source/modules/juce_core/threads/juce_HighResolutionTimer.h create mode 100644 source/modules/juce_core/threads/juce_InterProcessLock.h create mode 100644 source/modules/juce_core/threads/juce_Process.h create mode 100644 source/modules/juce_core/threads/juce_ReadWriteLock.cpp create mode 100644 source/modules/juce_core/threads/juce_ReadWriteLock.h create mode 100644 source/modules/juce_core/threads/juce_ScopedLock.h create mode 100644 source/modules/juce_core/threads/juce_ScopedReadLock.h create mode 100644 source/modules/juce_core/threads/juce_ScopedWriteLock.h create mode 100644 source/modules/juce_core/threads/juce_SpinLock.h create mode 100644 source/modules/juce_core/threads/juce_Thread.cpp create mode 100644 source/modules/juce_core/threads/juce_Thread.h create mode 100644 source/modules/juce_core/threads/juce_ThreadLocalValue.h create mode 100644 source/modules/juce_core/threads/juce_ThreadPool.cpp create mode 100644 source/modules/juce_core/threads/juce_ThreadPool.h create mode 100644 source/modules/juce_core/threads/juce_TimeSliceThread.cpp create mode 100644 source/modules/juce_core/threads/juce_TimeSliceThread.h create mode 100644 source/modules/juce_core/threads/juce_WaitableEvent.h create mode 100644 source/modules/juce_core/time/juce_PerformanceCounter.cpp create mode 100644 source/modules/juce_core/time/juce_PerformanceCounter.h create mode 100644 source/modules/juce_core/time/juce_RelativeTime.cpp create mode 100644 source/modules/juce_core/time/juce_RelativeTime.h create mode 100644 source/modules/juce_core/time/juce_Time.cpp create mode 100644 source/modules/juce_core/time/juce_Time.h create mode 100644 source/modules/juce_core/unit_tests/juce_UnitTest.cpp create mode 100644 source/modules/juce_core/unit_tests/juce_UnitTest.h create mode 100644 source/modules/juce_core/xml/juce_XmlDocument.cpp create mode 100644 source/modules/juce_core/xml/juce_XmlDocument.h create mode 100644 source/modules/juce_core/xml/juce_XmlElement.cpp create mode 100644 source/modules/juce_core/xml/juce_XmlElement.h create mode 100644 source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp create mode 100644 source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h create mode 100644 source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp create mode 100644 source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h create mode 100644 source/modules/juce_core/zip/juce_ZipFile.cpp create mode 100644 source/modules/juce_core/zip/juce_ZipFile.h create mode 100644 source/modules/juce_core/zip/zlib/README create mode 100644 source/modules/juce_core/zip/zlib/adler32.c create mode 100644 source/modules/juce_core/zip/zlib/compress.c create mode 100644 source/modules/juce_core/zip/zlib/crc32.c create mode 100644 source/modules/juce_core/zip/zlib/crc32.h create mode 100644 source/modules/juce_core/zip/zlib/deflate.c create mode 100644 source/modules/juce_core/zip/zlib/deflate.h create mode 100644 source/modules/juce_core/zip/zlib/infback.c create mode 100644 source/modules/juce_core/zip/zlib/inffast.c create mode 100644 source/modules/juce_core/zip/zlib/inffast.h create mode 100644 source/modules/juce_core/zip/zlib/inffixed.h create mode 100644 source/modules/juce_core/zip/zlib/inflate.c create mode 100644 source/modules/juce_core/zip/zlib/inflate.h create mode 100644 source/modules/juce_core/zip/zlib/inftrees.c create mode 100644 source/modules/juce_core/zip/zlib/inftrees.h create mode 100644 source/modules/juce_core/zip/zlib/trees.c create mode 100644 source/modules/juce_core/zip/zlib/trees.h create mode 100644 source/modules/juce_core/zip/zlib/uncompr.c create mode 100644 source/modules/juce_core/zip/zlib/zconf.h create mode 100644 source/modules/juce_core/zip/zlib/zconf.in.h create mode 100644 source/modules/juce_core/zip/zlib/zlib.h create mode 100644 source/modules/juce_core/zip/zlib/zutil.c create mode 100644 source/modules/juce_core/zip/zlib/zutil.h diff --git a/source/modules/JuceHeader.h b/source/modules/JuceHeader.h new file mode 100755 index 000000000..1cba80258 --- /dev/null +++ b/source/modules/JuceHeader.h @@ -0,0 +1,35 @@ +/* + * Carla Juce setup + * Copyright (C) 2013 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the doc/GPL.txt file. + */ + +#ifndef CARLA_JUCE_HEADER_H_INCLUDED +#define CARLA_JUCE_HEADER_H_INCLUDED + +#include "juce_core/AppConfig.h" +#include "juce_core/juce_core.h" + +//#include "modules/juce_audio_basics/juce_audio_basics.h" +//#include "modules/juce_audio_devices/juce_audio_devices.h" +//#include "modules/juce_audio_formats/juce_audio_formats.h" +//#include "modules/juce_audio_processors/juce_audio_processors.h" +//#include "modules/juce_audio_utils/juce_audio_utils.h" +//#include "modules/juce_data_structures/juce_data_structures.h" +//#include "modules/juce_events/juce_events.h" +//#include "modules/juce_graphics/juce_graphics.h" +//#include "modules/juce_gui_basics/juce_gui_basics.h" +//#include "modules/juce_gui_extra/juce_gui_extra.h" + +#endif // CARLA_JUCE_HEADER_H_INCLUDED diff --git a/source/modules/juce_core/AppConfig.h b/source/modules/juce_core/AppConfig.h new file mode 100755 index 000000000..c0e409842 --- /dev/null +++ b/source/modules/juce_core/AppConfig.h @@ -0,0 +1,98 @@ +/* + ============================================================================== + + Build options for juce_core static library + + ============================================================================== +*/ + +#ifndef CARLA_JUCE_CORE_APPCONFIG_H_INCLUDED +#define CARLA_JUCE_CORE_APPCONFIG_H_INCLUDED + +//============================================================================= +/** Config: JUCE_FORCE_DEBUG + + Normally, JUCE_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. +*/ +#if DEBUG + #define JUCE_FORCE_DEBUG 1 +#else + #define JUCE_FORCE_DEBUG 0 +#endif + +//============================================================================= +/** Config: JUCE_LOG_ASSERTIONS + + If this flag is enabled, the the jassert 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 jassert and jassertfalse macros will not be compiled in a + release build. + + @see jassert, jassertfalse, Logger +*/ +#if DEBUG + #define JUCE_LOG_ASSERTIONS 1 +#else + #define JUCE_LOG_ASSERTIONS 0 +#endif + +//============================================================================= +/** Config: JUCE_CHECK_MEMORY_LEAKS + + Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector + class and the JUCE_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes. +*/ +//#if DEBUG +#define JUCE_CHECK_MEMORY_LEAKS 1 +//#else +// #define JUCE_CHECK_MEMORY_LEAKS 0 +//#endif + +//============================================================================= +/** Config: JUCE_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. +*/ +#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 + +/* Config: JUCE_INCLUDE_ZLIB_CODE + This can be used to disable Juce'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 JUCE_ZLIB_INCLUDE_PATH, to + specify the path where your zlib headers live. +*/ +#define JUCE_INCLUDE_ZLIB_CODE 1 +//#define JUCE_ZLIB_INCLUDE_PATH + +/* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS + If enabled, this will add some exception-catching code to forward unhandled exceptions + to your JUCEApplication::unhandledException() callback. +*/ +#if DEBUG + #define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 +#else + #define JUCE_CATCH_UNHANDLED_EXCEPTIONS 0 +#endif + +#define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 +#define JUCE_MODULE_AVAILABLE_juce_audio_devices 1 +#define JUCE_MODULE_AVAILABLE_juce_audio_formats 1 +#define JUCE_MODULE_AVAILABLE_juce_audio_processors 0 +#define JUCE_MODULE_AVAILABLE_juce_audio_utils 0 +#define JUCE_MODULE_AVAILABLE_juce_core 1 +#define JUCE_MODULE_AVAILABLE_juce_cryptography 0 +#define JUCE_MODULE_AVAILABLE_juce_data_structures 0 +#define JUCE_MODULE_AVAILABLE_juce_events 1 +#define JUCE_MODULE_AVAILABLE_juce_graphics 0 +#define JUCE_MODULE_AVAILABLE_juce_gui_basics 0 +#define JUCE_MODULE_AVAILABLE_juce_gui_extra 0 +#define JUCE_MODULE_AVAILABLE_juce_opengl 0 +#define JUCE_MODULE_AVAILABLE_juce_video 0 + +#endif // CARLA_JUCE_CORE_APPCONFIG_H_INCLUDED diff --git a/source/modules/juce_core/Makefile b/source/modules/juce_core/Makefile new file mode 100644 index 000000000..084ae31c2 --- /dev/null +++ b/source/modules/juce_core/Makefile @@ -0,0 +1,88 @@ +#!/usr/bin/make -f +# Makefile for juce_core # +# ---------------------- # +# Created by falkTX +# + +include ../../Makefile.mk + +# -------------------------------------------------------------- + +BUILD_CXX_FLAGS += -I. + +ifeq ($(MACOS),true) +BUILD_CXX_FLAGS += -objc++ +OBJS = juce_core.mm.o +OBJS_posix32 = juce_core.mm.posix32.o +OBJS_posix64 = juce_core.mm.posix64.o +else +OBJS = juce_core.cpp.o +OBJS_posix32 = juce_core.cpp.posix32.o +OBJS_posix64 = juce_core.cpp.posix64.o +endif + +OBJS_win32 = juce_core.cpp.win32.o +OBJS_win64 = juce_core.cpp.win64.o + +# -------------------------------------------------------------- + +all: ../juce_core.a + +posix32: ../juce_core.posix32.a +posix64: ../juce_core.posix64.a +win32: ../juce_core.win32.a +win64: ../juce_core.win64.a + +# -------------------------------------------------------------- + +../juce_core.a: $(OBJS) + $(AR) rs $@ $^ + +../juce_core.posix32.a: $(OBJS_posix32) + $(AR) rs $@ $^ + +../juce_core.posix64.a: $(OBJS_posix64) + $(AR) rs $@ $^ + +../juce_core.win32.a: $(OBJS_win32) + $(AR) rs $@ $^ + +../juce_core.win64.a: $(OBJS_win64) + $(AR) rs $@ $^ + +../juce_core.dll: $(OBJS) + $(CXX) $^ -shared $(LINK_FLAGS) -o $@ $(CMD_STRIP) $@ + +../juce_core.dylib: $(OBJS) + $(CXX) $^ -dynamiclib $(LINK_FLAGS) -o $@ $(CMD_STRIP) $@ + +../juce_core.so: $(OBJS) + $(CXX) $^ -shared $(LINK_FLAGS) -o $@ $(CMD_STRIP) $@ + +# -------------------------------------------------------------- + +%.cpp.o: %.cpp + $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ + +%.mm.o: %.mm + $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ + +%.posix32.o: % + $(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ + +%.posix64.o: % + $(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ + +%.win32.o: % + $(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ + +%.win64.o: % + $(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ + +# -------------------------------------------------------------- + +clean: + rm -f *.o ../juce_core.* + +debug: + $(MAKE) DEBUG=true diff --git a/source/modules/juce_core/containers/juce_AbstractFifo.cpp b/source/modules/juce_core/containers/juce_AbstractFifo.cpp new file mode 100644 index 000000000..666cc50d0 --- /dev/null +++ b/source/modules/juce_core/containers/juce_AbstractFifo.cpp @@ -0,0 +1,234 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +AbstractFifo::AbstractFifo (const int capacity) noexcept + : bufferSize (capacity) +{ + jassert (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 +{ + jassert (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 = jmin (numToWrite, freeSpace - 1); + + if (numToWrite <= 0) + { + startIndex1 = 0; + startIndex2 = 0; + blockSize1 = 0; + blockSize2 = 0; + } + else + { + startIndex1 = ve; + startIndex2 = 0; + blockSize1 = jmin (bufferSize - ve, numToWrite); + numToWrite -= blockSize1; + blockSize2 = numToWrite <= 0 ? 0 : jmin (numToWrite, vs); + } +} + +void AbstractFifo::finishedWrite (int numWritten) noexcept +{ + jassert (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 = jmin (numWanted, numReady); + + if (numWanted <= 0) + { + startIndex1 = 0; + startIndex2 = 0; + blockSize1 = 0; + blockSize2 = 0; + } + else + { + startIndex1 = vs; + startIndex2 = 0; + blockSize1 = jmin (bufferSize - vs, numWanted); + numWanted -= blockSize1; + blockSize2 = numWanted <= 0 ? 0 : jmin (numWanted, ve); + } +} + +void AbstractFifo::finishedRead (int numRead) noexcept +{ + jassert (numRead >= 0 && numRead <= bufferSize); + + int newStart = validStart.value + numRead; + if (newStart >= bufferSize) + newStart -= bufferSize; + + validStart = newStart; +} + +//============================================================================== +//============================================================================== +#if JUCE_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); + + jassert (size1 >= 0 && size2 >= 0); + jassert (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())); + jassert (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/source/modules/juce_core/containers/juce_AbstractFifo.h b/source/modules/juce_core/containers/juce_AbstractFifo.h new file mode 100644 index 000000000..f656a107d --- /dev/null +++ b/source/modules/juce_core/containers/juce_AbstractFifo.h @@ -0,0 +1,222 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ABSTRACTFIFO_H_INCLUDED +#define JUCE_ABSTRACTFIFO_H_INCLUDED + +#include "../memory/juce_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 JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) +}; + + +#endif // JUCE_ABSTRACTFIFO_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_Array.h b/source/modules/juce_core/containers/juce_Array.h new file mode 100644 index 000000000..710dcba83 --- /dev/null +++ b/source/modules/juce_core/containers/juce_Array.h @@ -0,0 +1,1064 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ARRAY_H_INCLUDED +#define JUCE_ARRAY_H_INCLUDED + +#include "juce_ArrayAllocationBase.h" +#include "juce_ElementComparator.h" +#include "../threads/juce_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 JUCE_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); + swapWith (otherCopy); + } + + return *this; + } + + #if JUCE_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()); + + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return 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()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + 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()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + 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); + jassert (data.elements != nullptr); + + 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) + { + jassert (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()); + jassert (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. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherArrayType::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) + { + jassert (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)) + { + jassert (data.elements != nullptr); + 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 = jlimit (0, numUsed, startIndex + numberToRemove); + startIndex = jlimit (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; + + + //============================================================================== + // Note that the swapWithArray method has been replaced by a more flexible templated version, + // and renamed "swapWith" to be more consistent with the names used in other classes. + JUCE_DEPRECATED_WITH_BODY (void swapWithArray (Array& other) noexcept, { swapWith (other); }) + +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 > jmax (minimumAllocatedSize, numUsed * 2)) + data.shrinkToNoMoreThan (jmax (numUsed, jmax (minimumAllocatedSize, 64 / (int) sizeof (ElementType)))); + } +}; + + +#endif // JUCE_ARRAY_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_ArrayAllocationBase.h b/source/modules/juce_core/containers/juce_ArrayAllocationBase.h new file mode 100644 index 000000000..31aa256a6 --- /dev/null +++ b/source/modules/juce_core/containers/juce_ArrayAllocationBase.h @@ -0,0 +1,140 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ARRAYALLOCATIONBASE_H_INCLUDED +#define JUCE_ARRAYALLOCATIONBASE_H_INCLUDED + +#include "../memory/juce_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 JUCE_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); + + jassert (numAllocated <= 0 || elements != nullptr); + } + + /** 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: + JUCE_DECLARE_NON_COPYABLE (ArrayAllocationBase) +}; + + +#endif // JUCE_ARRAYALLOCATIONBASE_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_DynamicObject.cpp b/source/modules/juce_core/containers/juce_DynamicObject.cpp new file mode 100644 index 000000000..69e587feb --- /dev/null +++ b/source/modules/juce_core/containers/juce_DynamicObject.cpp @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/containers/juce_DynamicObject.h b/source/modules/juce_core/containers/juce_DynamicObject.h new file mode 100644 index 000000000..2b14aa587 --- /dev/null +++ b/source/modules/juce_core/containers/juce_DynamicObject.h @@ -0,0 +1,126 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_DYNAMICOBJECT_H_INCLUDED +#define JUCE_DYNAMICOBJECT_H_INCLUDED + +#include "juce_NamedValueSet.h" +#include "../memory/juce_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 JUCE_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; + + JUCE_LEAK_DETECTOR (DynamicObject) +}; + + + +#endif // JUCE_DYNAMICOBJECT_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_ElementComparator.h b/source/modules/juce_core/containers/juce_ElementComparator.h new file mode 100644 index 000000000..f2b0b55c4 --- /dev/null +++ b/source/modules/juce_core/containers/juce_ElementComparator.h @@ -0,0 +1,279 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ELEMENTCOMPARATOR_H_INCLUDED +#define JUCE_ELEMENTCOMPARATOR_H_INCLUDED + + +//============================================================================== +/** + 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; + + jassert (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) +{ + jassert (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 // JUCE_ELEMENTCOMPARATOR_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_HashMap.h b/source/modules/juce_core/containers/juce_HashMap.h new file mode 100644 index 000000000..2608ad5c3 --- /dev/null +++ b/source/modules/juce_core/containers/juce_HashMap.h @@ -0,0 +1,460 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_HASHMAP_H_INCLUDED +#define JUCE_HASHMAP_H_INCLUDED + +#include "juce_OwnedArray.h" +#include "juce_LinkedListPointer.h" +#include "../memory/juce_ScopedPointer.h" + + +//============================================================================== +/** + A simple class to generate hash functions for some primitive types, intended for + use with the HashMap class. + @see HashMap +*/ +struct DefaultHashFunctions +{ + /** Generates a simple hash from an integer. */ + int generateHash (const int key, const int upperLimit) const noexcept { return std::abs (key) % upperLimit; } + /** Generates a simple hash from an int64. */ + int generateHash (const int64 key, const int upperLimit) const noexcept { return std::abs ((int) key) % upperLimit; } + /** Generates a simple hash from a string. */ + int generateHash (const String& key, const int upperLimit) const noexcept { return (int) (((uint32) key.hashCode()) % (uint32) upperLimit); } + /** Generates a simple hash from a variant. */ + int generateHash (const var& key, const int upperLimit) const 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 + { + 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 HashFunctionType 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 + + @tparam HashFunctionType The class of hash function, which must be copy-constructible. + @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(). + + @param hashFunction An instance of HashFunctionType, which will be copied and + stored to use with the HashMap. This parameter can be omitted + if HashFunctionType has a default constructor. + */ + explicit HashMap (int numberOfSlots = defaultHashTableSize, + HashFunctionType hashFunction = HashFunctionType()) + : hashFunctionToUse (hashFunction), 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. */ + template + void swapWith (OtherHashMapType& otherHashMap) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherHashMapType::ScopedLockType lock2 (otherHashMap.getLock()); + + slots.swapWith (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; + + JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Iterator) + }; + +private: + //============================================================================== + enum { defaultHashTableSize = 101 }; + friend class Iterator; + + HashFunctionType hashFunctionToUse; + Array slots; + int totalNumItems; + TypeOfCriticalSectionToUse lock; + + int generateHashFor (KeyTypeParameter key) const + { + const int hash = hashFunctionToUse.generateHash (key, getNumSlots()); + jassert (isPositiveAndBelow (hash, getNumSlots())); // your hash function is generating out-of-range numbers! + return hash; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashMap) +}; + + +#endif // JUCE_HASHMAP_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_LinkedListPointer.h b/source/modules/juce_core/containers/juce_LinkedListPointer.h new file mode 100644 index 000000000..a27e52183 --- /dev/null +++ b/source/modules/juce_core/containers/juce_LinkedListPointer.h @@ -0,0 +1,371 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LINKEDLISTPOINTER_H_INCLUDED +#define JUCE_LINKEDLISTPOINTER_H_INCLUDED + + +//============================================================================== +/** + 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 JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + LinkedListPointer (LinkedListPointer&& other) noexcept + : item (other.item) + { + other.item = nullptr; + } + + LinkedListPointer& operator= (LinkedListPointer&& other) noexcept + { + jassert (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) + { + jassert (newItem != nullptr); + jassert (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) + { + jassert (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 + { + jassert (newItem != nullptr); + jassert (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 + { + jassert (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. + jassert (endOfListPointer.item == nullptr); + } + + /** Appends an item to the list. */ + void append (ObjectType* const newItem) noexcept + { + *endOfList = newItem; + endOfList = &(newItem->nextListItem); + } + + private: + LinkedListPointer* endOfList; + + JUCE_DECLARE_NON_COPYABLE (Appender) + }; + +private: + //============================================================================== + ObjectType* item; + + JUCE_DECLARE_NON_COPYABLE (LinkedListPointer) +}; + + +#endif // JUCE_LINKEDLISTPOINTER_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_NamedValueSet.cpp b/source/modules/juce_core/containers/juce_NamedValueSet.cpp new file mode 100644 index 000000000..5ea71a533 --- /dev/null +++ b/source/modules/juce_core/containers/juce_NamedValueSet.cpp @@ -0,0 +1,309 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_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 JUCE_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 JUCE_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]; + jassert (v != nullptr); + return v->name; +} + +const var& NamedValueSet::getValueAt (const int index) const +{ + const NamedValue* const v = values[index]; + jassert (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! + jassert (! i->value.isObject()); + jassert (! i->value.isMethod()); + jassert (! i->value.isArray()); + + xml.setAttribute (i->name.toString(), + i->value.toString()); + } + } +} diff --git a/source/modules/juce_core/containers/juce_NamedValueSet.h b/source/modules/juce_core/containers/juce_NamedValueSet.h new file mode 100644 index 000000000..f1c4c35c9 --- /dev/null +++ b/source/modules/juce_core/containers/juce_NamedValueSet.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NAMEDVALUESET_H_INCLUDED +#define JUCE_NAMEDVALUESET_H_INCLUDED + +#include "juce_Variant.h" +#include "../containers/juce_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 JUCE_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 JUCE_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 JUCE_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 JUCE_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: + JUCE_LEAK_DETECTOR (NamedValue) + }; + + friend class LinkedListPointer; + LinkedListPointer values; + + friend class JSONFormatter; +}; + + +#endif // JUCE_NAMEDVALUESET_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_OwnedArray.h b/source/modules/juce_core/containers/juce_OwnedArray.h new file mode 100644 index 000000000..b59e25d23 --- /dev/null +++ b/source/modules/juce_core/containers/juce_OwnedArray.h @@ -0,0 +1,897 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OWNEDARRAY_H_INCLUDED +#define JUCE_OWNEDARRAY_H_INCLUDED + +#include "juce_ArrayAllocationBase.h" +#include "juce_ElementComparator.h" +#include "../threads/juce_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 JUCE_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 (bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + + if (deleteObjects) + deleteAllObjects(); + + data.setAllocatedSize (0); + numUsed = 0; + } + + //============================================================================== + /** Clears the array, optionally deleting the objects inside it first. */ + void clearQuick (bool deleteObjects) + { + const ScopedLockType lock (getLock()); + + if (deleteObjects) + deleteAllObjects(); + + 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()); + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return 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()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + 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 + */ + ObjectClass* add (ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + data.elements [numUsed++] = const_cast (newObject); + return 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, + ObjectClass* const newObject) noexcept + { + if (indexToInsertAt >= 0) + { + const ScopedLockType lock (getLock()); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + 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 (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); + jassert (numElementsToAdd <= 0 || data.elements != nullptr); + + 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); + jassert (numElementsToAdd <= 0 || data.elements != nullptr); + + 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 = jlimit (0, numUsed, startIndex + numberToRemove); + startIndex = jlimit (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. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherArrayType::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; + + + //============================================================================== + // Note that the swapWithArray method has been replaced by a more flexible templated version, + // and renamed "swapWith" to be more consistent with the names used in other classes. + JUCE_DEPRECATED_WITH_BODY (void swapWithArray (OwnedArray& other) noexcept, { swapWith (other); }) + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void deleteAllObjects() + { + while (numUsed > 0) + delete data.elements [--numUsed]; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OwnedArray) +}; + + +#endif // JUCE_OWNEDARRAY_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_PropertySet.cpp b/source/modules/juce_core/containers/juce_PropertySet.cpp new file mode 100644 index 000000000..ba23e7ada --- /dev/null +++ b/source/modules/juce_core/containers/juce_PropertySet.cpp @@ -0,0 +1,223 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) +{ + jassert (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/source/modules/juce_core/containers/juce_PropertySet.h b/source/modules/juce_core/containers/juce_PropertySet.h new file mode 100644 index 000000000..20ea4a160 --- /dev/null +++ b/source/modules/juce_core/containers/juce_PropertySet.h @@ -0,0 +1,219 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PROPERTYSET_H_INCLUDED +#define JUCE_PROPERTYSET_H_INCLUDED + +#include "../text/juce_StringPairArray.h" +#include "../xml/juce_XmlElement.h" +#include "../containers/juce_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 JUCE_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; + + JUCE_LEAK_DETECTOR (PropertySet) +}; + + +#endif // JUCE_PROPERTYSET_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_ReferenceCountedArray.h b/source/modules/juce_core/containers/juce_ReferenceCountedArray.h new file mode 100644 index 000000000..0e0e5cd4d --- /dev/null +++ b/source/modules/juce_core/containers/juce_ReferenceCountedArray.h @@ -0,0 +1,872 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED +#define JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED + +#include "../memory/juce_ReferenceCountedObject.h" +#include "juce_ArrayAllocationBase.h" +#include "juce_ElementComparator.h" +#include "../threads/juce_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); + swapWith (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); + swapWith (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(); + + jassert (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()); + jassert (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 + */ + ObjectClass* add (ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + data.elements [numUsed++] = newObject; + + if (newObject != nullptr) + newObject->incReferenceCount(); + + return newObject; + } + + /** 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 + */ + ObjectClass* insert (int indexToInsertAt, + ObjectClass* const newObject) noexcept + { + if (indexToInsertAt >= 0) + { + const ScopedLockType lock (getLock()); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + 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; + + return newObject; + } + else + { + return 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); + jassert (data.elements != nullptr); + 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 = jlimit (0, numUsed, startIndex); + const int endIndex = jlimit (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. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherArrayType::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; + + + //============================================================================== + // Note that the swapWithArray method has been replaced by a more flexible templated version, + // and renamed "swapWith" to be more consistent with the names used in other classes. + JUCE_DEPRECATED_WITH_BODY (void swapWithArray (ReferenceCountedArray& other) noexcept, { swapWith (other); }) + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; +}; + + +#endif // JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_ScopedValueSetter.h b/source/modules/juce_core/containers/juce_ScopedValueSetter.h new file mode 100644 index 000000000..13b871ef2 --- /dev/null +++ b/source/modules/juce_core/containers/juce_ScopedValueSetter.h @@ -0,0 +1,100 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDVALUESETTER_H_INCLUDED +#define JUCE_SCOPEDVALUESETTER_H_INCLUDED + + +//============================================================================== +/** + 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; + + JUCE_DECLARE_NON_COPYABLE (ScopedValueSetter) +}; + + +#endif // JUCE_SCOPEDVALUESETTER_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_SortedSet.h b/source/modules/juce_core/containers/juce_SortedSet.h new file mode 100644 index 000000000..b7fa061d5 --- /dev/null +++ b/source/modules/juce_core/containers/juce_SortedSet.h @@ -0,0 +1,497 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SORTEDSET_H_INCLUDED +#define JUCE_SORTEDSET_H_INCLUDED + +#include "juce_ArrayAllocationBase.h" +#include "../threads/juce_CriticalSection.h" + +#if JUCE_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()); + jassert (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. + */ + template + void swapWith (OtherSetType& otherSet) noexcept + { + data.swapWith (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 JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_SORTEDSET_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_SparseSet.h b/source/modules/juce_core/containers/juce_SparseSet.h new file mode 100644 index 000000000..5064ae0cc --- /dev/null +++ b/source/modules/juce_core/containers/juce_SparseSet.h @@ -0,0 +1,301 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SPARSESET_H_INCLUDED +#define JUCE_SPARSESET_H_INCLUDED + +#include "../maths/juce_Range.h" +#include "../threads/juce_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) + { + jassert ((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) + { + jassert (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) + { + jassert (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 (jmin (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() + { + jassert ((values.size() & 1) == 0); + + for (int i = values.size(); --i > 0;) + if (values.getUnchecked(i) == values.getUnchecked (i - 1)) + values.removeRange (--i, 2); + } +}; + + + +#endif // JUCE_SPARSESET_H_INCLUDED diff --git a/source/modules/juce_core/containers/juce_Variant.cpp b/source/modules/juce_core/containers/juce_Variant.cpp new file mode 100644 index 000000000..a302d363d --- /dev/null +++ b/source/modules/juce_core/containers/juce_Variant.cpp @@ -0,0 +1,709 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 ? (juce_wchar) '1' : (juce_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 JUCE_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 +{ + jassert (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! + jassert (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! + jassert (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/source/modules/juce_core/containers/juce_Variant.h b/source/modules/juce_core/containers/juce_Variant.h new file mode 100644 index 000000000..92a75b9dc --- /dev/null +++ b/source/modules/juce_core/containers/juce_Variant.h @@ -0,0 +1,307 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_VARIANT_H_INCLUDED +#define JUCE_VARIANT_H_INCLUDED + +#include "../text/juce_Identifier.h" +#include "../streams/juce_OutputStream.h" +#include "../streams/juce_InputStream.h" +#include "../containers/juce_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 JUCE_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 JUCE_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 // JUCE_VARIANT_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_DirectoryIterator.cpp b/source/modules/juce_core/files/juce_DirectoryIterator.cpp new file mode 100644 index 000000000..7da018e1a --- /dev/null +++ b/source/modules/juce_core/files/juce_DirectoryIterator.cpp @@ -0,0 +1,159 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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! + jassert ((type & (File::findFiles | File::findDirectories)) != 0); + jassert (type > 0 && type <= 7); +} + +DirectoryIterator::~DirectoryIterator() +{ +} + +StringArray DirectoryIterator::parseWildcards (const String& pattern) +{ + StringArray s; + s.addTokens (pattern, ";,", "\"'"); + s.trim(); + s.removeEmptyStrings(); + return s; +} + +bool DirectoryIterator::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; +} + +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! + jassert (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/source/modules/juce_core/files/juce_DirectoryIterator.h b/source/modules/juce_core/files/juce_DirectoryIterator.h new file mode 100644 index 000000000..fcc50569c --- /dev/null +++ b/source/modules/juce_core/files/juce_DirectoryIterator.h @@ -0,0 +1,162 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_DIRECTORYITERATOR_H_INCLUDED +#define JUCE_DIRECTORYITERATOR_H_INCLUDED + +#include "juce_File.h" +#include "../memory/juce_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 JUCE_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; + + JUCE_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; + + static StringArray parseWildcards (const String& pattern); + static bool fileMatches (const StringArray& wildCards, const String& filename); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryIterator) +}; + +#endif // JUCE_DIRECTORYITERATOR_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_File.cpp b/source/modules/juce_core/files/juce_File.cpp new file mode 100644 index 000000000..5c8a8352f --- /dev/null +++ b/source/modules/juce_core/files/juce_File.cpp @@ -0,0 +1,1091 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_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 JUCE_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. + jassert ((! 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 JUCE_DEBUG || JUCE_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 JUCE_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 JUCE_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 JUCE_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 JUCE_WINDOWS + relativePath = relativePath.replaceCharacter ('/', '\\'); + #endif + + while (relativePath[0] == '.') + { + const juce_wchar secondChar = relativePath[1]; + + if (secondChar == '.') + { + const juce_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 +{ + jassert (((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 > jmax (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 juce_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 juce_wchar c1 = thisPathIter.getAndAdvance(); + const juce_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 JUCE_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 JUCE_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 ("Juce 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/source/modules/juce_core/files/juce_File.h b/source/modules/juce_core/files/juce_File.h new file mode 100644 index 000000000..ad3cb19fb --- /dev/null +++ b/source/modules/juce_core/files/juce_File.h @@ -0,0 +1,959 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILE_H_INCLUDED +#define JUCE_FILE_H_INCLUDED + +#include "../containers/juce_Array.h" +#include "../time/juce_Time.h" +#include "../text/juce_StringArray.h" +#include "../memory/juce_MemoryBlock.h" +#include "../memory/juce_ScopedPointer.h" +#include "../misc/juce_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 JUCE_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& absolutePath); + + /** Creates a copy of another file object. */ + File (const File&); + + /** 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& newAbsolutePath); + + /** Copies from another file object. */ + File& operator= (const File& otherFile); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + File (File&&) noexcept; + File& operator= (File&&) 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. 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 that represents a relative (or absolute) sub-path of the current one. + + 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 ("haddock/foo.txt") will produce "/moose/fish/haddock/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 relativeOrAbsolutePath) 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&) const; + /** Compares the pathnames for two files. */ + bool operator!= (const File&) const; + /** Compares the pathnames for two files. */ + bool operator< (const File&) const; + /** Compares the pathnames for two files. */ + bool operator> (const File&) 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 JUCE_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 juce_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; + + //============================================================================== + /** Returns a version of a filename with any illegal characters removed. + + 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 - use + createLegalPathName() for that. + + @see createLegalPathName + */ + static String createLegalFileName (const String& fileNameToFix); + + /** Returns a version of a path with any illegal characters removed. + + 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& absolutePath) 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 JUCE_MAC || JUCE_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 JUCE_MAC || DOXYGEN + /** OSX ONLY - Adds this file to the OSX dock */ + void addToDock() const; + #endif + + #if JUCE_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; +}; + +#endif // JUCE_FILE_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_FileInputStream.cpp b/source/modules/juce_core/files/juce_FileInputStream.cpp new file mode 100644 index 000000000..426f3adc7 --- /dev/null +++ b/source/modules/juce_core/files/juce_FileInputStream.cpp @@ -0,0 +1,95 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +int64 juce_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) +{ + jassert (openedOk()); + jassert (buffer != nullptr && bytesToRead >= 0); + + if (needToSeek) + { + if (juce_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) +{ + jassert (openedOk()); + + if (pos != currentPosition) + { + pos = jlimit ((int64) 0, getTotalLength(), pos); + + needToSeek |= (currentPosition != pos); + currentPosition = pos; + } + + return true; +} diff --git a/source/modules/juce_core/files/juce_FileInputStream.h b/source/modules/juce_core/files/juce_FileInputStream.h new file mode 100644 index 000000000..6a452f71c --- /dev/null +++ b/source/modules/juce_core/files/juce_FileInputStream.h @@ -0,0 +1,99 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEINPUTSTREAM_H_INCLUDED +#define JUCE_FILEINPUTSTREAM_H_INCLUDED + +#include "juce_File.h" +#include "../streams/juce_InputStream.h" + + +//============================================================================== +/** + An input stream that reads from a local file. + + @see InputStream, FileOutputStream, File::createInputStream +*/ +class JUCE_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() override; + int read (void* destBuffer, int maxBytesToRead) override; + bool isExhausted() override; + int64 getPosition() override; + bool setPosition (int64 pos) override; + +private: + //============================================================================== + File file; + void* fileHandle; + int64 currentPosition; + Result status; + bool needToSeek; + + void openHandle(); + void closeHandle(); + size_t readInternal (void* buffer, size_t numBytes); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) +}; + +#endif // JUCE_FILEINPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_FileOutputStream.cpp b/source/modules/juce_core/files/juce_FileOutputStream.cpp new file mode 100644 index 000000000..fd14a4a9d --- /dev/null +++ b/source/modules/juce_core/files/juce_FileOutputStream.cpp @@ -0,0 +1,135 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +int64 juce_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) jmax (bufferSize_, 16)) +{ + openHandle(); +} + +FileOutputStream::~FileOutputStream() +{ + flushBuffer(); + flushInternal(); + closeHandle(); +} + +int64 FileOutputStream::getPosition() +{ + return currentPosition; +} + +bool FileOutputStream::setPosition (int64 newPosition) +{ + if (newPosition != currentPosition) + { + flushBuffer(); + currentPosition = juce_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) +{ + jassert (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; +} + +bool FileOutputStream::writeRepeatedByte (uint8 byte, size_t numBytes) +{ + jassert (((ssize_t) numBytes) >= 0); + + if (bytesInBuffer + numBytes < bufferSize) + { + memset (buffer + bytesInBuffer, byte, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + return true; + } + + return OutputStream::writeRepeatedByte (byte, numBytes); +} diff --git a/source/modules/juce_core/files/juce_FileOutputStream.h b/source/modules/juce_core/files/juce_FileOutputStream.h new file mode 100644 index 000000000..73ad20d49 --- /dev/null +++ b/source/modules/juce_core/files/juce_FileOutputStream.h @@ -0,0 +1,119 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEOUTPUTSTREAM_H_INCLUDED +#define JUCE_FILEOUTPUTSTREAM_H_INCLUDED + +#include "juce_File.h" +#include "../streams/juce_OutputStream.h" + + +//============================================================================== +/** + An output stream that writes into a local file. + + @see OutputStream, FileInputStream, File::createOutputStream +*/ +class JUCE_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() override; + int64 getPosition() override; + bool setPosition (int64) override; + bool write (const void*, size_t) override; + bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) override; + + +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); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileOutputStream) +}; + +#endif // JUCE_FILEOUTPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_FileSearchPath.cpp b/source/modules/juce_core/files/juce_FileSearchPath.cpp new file mode 100644 index 000000000..053e92a1a --- /dev/null +++ b/source/modules/juce_core/files/juce_FileSearchPath.cpp @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/files/juce_FileSearchPath.h b/source/modules/juce_core/files/juce_FileSearchPath.h new file mode 100644 index 000000000..a35f5aed4 --- /dev/null +++ b/source/modules/juce_core/files/juce_FileSearchPath.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILESEARCHPATH_H_INCLUDED +#define JUCE_FILESEARCHPATH_H_INCLUDED + +#include "juce_File.h" +#include "../text/juce_StringArray.h" + + +//============================================================================== +/** + Encapsulates a set of folders that make up a search path. + + @see File +*/ +class JUCE_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); + + JUCE_LEAK_DETECTOR (FileSearchPath) +}; + +#endif // JUCE_FILESEARCHPATH_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_MemoryMappedFile.h b/source/modules/juce_core/files/juce_MemoryMappedFile.h new file mode 100644 index 000000000..943edb63d --- /dev/null +++ b/source/modules/juce_core/files/juce_MemoryMappedFile.h @@ -0,0 +1,116 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYMAPPEDFILE_H_INCLUDED +#define JUCE_MEMORYMAPPEDFILE_H_INCLUDED + +#include "juce_File.h" + +//============================================================================== +/** + Maps a file into virtual memory for easy reading and/or writing. +*/ +class JUCE_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 JUCE_WINDOWS + void* fileHandle; + #else + int fileHandle; + #endif + + void openInternal (const File&, AccessMode); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedFile) +}; + + +#endif // JUCE_MEMORYMAPPEDFILE_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_TemporaryFile.cpp b/source/modules/juce_core/files/juce_TemporaryFile.cpp new file mode 100644 index 000000000..d5789b65e --- /dev/null +++ b/source/modules/juce_core/files/juce_TemporaryFile.cpp @@ -0,0 +1,117 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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! + jassert (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! + jassert (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/source/modules/juce_core/files/juce_TemporaryFile.h b/source/modules/juce_core/files/juce_TemporaryFile.h new file mode 100644 index 000000000..d33dc36a2 --- /dev/null +++ b/source/modules/juce_core/files/juce_TemporaryFile.h @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TEMPORARYFILE_H_INCLUDED +#define JUCE_TEMPORARYFILE_H_INCLUDED + +#include "juce_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 JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryFile) +}; + +#endif // JUCE_TEMPORARYFILE_H_INCLUDED diff --git a/source/modules/juce_core/json/juce_JSON.cpp b/source/modules/juce_core/json/juce_JSON.cpp new file mode 100644 index 000000000..b49a39ceb --- /dev/null +++ b/source/modules/juce_core/json/juce_JSON.cpp @@ -0,0 +1,650 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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'; + jassert (intValue >= 0 && intValue < 10); + + for (;;) + { + String::CharPointerType previousChar (t); + const juce_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 juce_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 juce_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 juce_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 juce_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 juce_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 (;;) + { + juce_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 = (juce_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! + jassert (! (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 juce_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 JUCE_UNIT_TESTS + +class JSONTests : public UnitTest +{ +public: + JSONTests() : UnitTest ("JSON") {} + + static String createRandomWideCharString (Random& r) + { + juce_wchar buffer[40] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_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/source/modules/juce_core/json/juce_JSON.h b/source/modules/juce_core/json/juce_JSON.h new file mode 100644 index 000000000..a443c7a96 --- /dev/null +++ b/source/modules/juce_core/json/juce_JSON.h @@ -0,0 +1,118 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_JSON_H_INCLUDED +#define JUCE_JSON_H_INCLUDED + +#include "../misc/juce_Result.h" +#include "../containers/juce_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 JUCE_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 // JUCE_JSON_H_INCLUDED diff --git a/source/modules/juce_core/juce_core.cpp b/source/modules/juce_core/juce_core.cpp new file mode 100644 index 000000000..1e6c132c2 --- /dev/null +++ b/source/modules/juce_core/juce_core.cpp @@ -0,0 +1,218 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#if defined (JUCE_CORE_H_INCLUDED) && ! JUCE_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 JUCE cpp file" +#endif + +// Your project must contain an AppConfig.h file with your project-specific settings in it, +// and your header search path must make it accessible to the module's files. +#include "AppConfig.h" + +//============================================================================== +#include "native/juce_BasicNativeHeaders.h" +#include "juce_core.h" + +#include +#include +#include + +#if ! JUCE_ANDROID + #include +#endif + +#if JUCE_WINDOWS + #include + #include + #include + + #if ! JUCE_MINGW + #include + + #if ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #pragma comment (lib, "DbgHelp.lib") + #endif + #endif + + #if JUCE_MINGW + #include + #endif + +#else + #if JUCE_LINUX || JUCE_ANDROID + #include + #include + #include + #include + #include + #endif + + #if JUCE_LINUX + #include + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + + #if ! JUCE_ANDROID + #include + #endif +#endif + +#if JUCE_MAC || JUCE_IOS + #include + #include +#endif + +#if JUCE_ANDROID + #include +#endif + + +//============================================================================== +namespace juce +{ + +#include "containers/juce_AbstractFifo.cpp" +#include "containers/juce_DynamicObject.cpp" +#include "containers/juce_NamedValueSet.cpp" +#include "containers/juce_PropertySet.cpp" +#include "containers/juce_Variant.cpp" +#include "files/juce_DirectoryIterator.cpp" +#include "files/juce_File.cpp" +#include "files/juce_FileInputStream.cpp" +#include "files/juce_FileOutputStream.cpp" +#include "files/juce_FileSearchPath.cpp" +#include "files/juce_TemporaryFile.cpp" +#include "json/juce_JSON.cpp" +#include "logging/juce_FileLogger.cpp" +#include "logging/juce_Logger.cpp" +#include "maths/juce_BigInteger.cpp" +#include "maths/juce_Expression.cpp" +#include "maths/juce_Random.cpp" +#include "memory/juce_MemoryBlock.cpp" +#include "misc/juce_Result.cpp" +#include "misc/juce_Uuid.cpp" +#include "network/juce_MACAddress.cpp" +#include "network/juce_NamedPipe.cpp" +#include "network/juce_Socket.cpp" +#include "network/juce_URL.cpp" +#include "network/juce_IPAddress.cpp" +#include "streams/juce_BufferedInputStream.cpp" +#include "streams/juce_FileInputSource.cpp" +#include "streams/juce_InputStream.cpp" +#include "streams/juce_MemoryInputStream.cpp" +#include "streams/juce_MemoryOutputStream.cpp" +#include "streams/juce_OutputStream.cpp" +#include "streams/juce_SubregionStream.cpp" +#include "system/juce_SystemStats.cpp" +#include "text/juce_CharacterFunctions.cpp" +#include "text/juce_Identifier.cpp" +#include "text/juce_LocalisedStrings.cpp" +#include "text/juce_String.cpp" +#include "text/juce_StringArray.cpp" +#include "text/juce_StringPairArray.cpp" +#include "text/juce_StringPool.cpp" +#include "text/juce_TextDiff.cpp" +#include "threads/juce_ChildProcess.cpp" +#include "threads/juce_ReadWriteLock.cpp" +#include "threads/juce_Thread.cpp" +#include "threads/juce_ThreadPool.cpp" +#include "threads/juce_TimeSliceThread.cpp" +#include "time/juce_PerformanceCounter.cpp" +#include "time/juce_RelativeTime.cpp" +#include "time/juce_Time.cpp" +#include "unit_tests/juce_UnitTest.cpp" +#include "xml/juce_XmlDocument.cpp" +#include "xml/juce_XmlElement.cpp" +#include "zip/juce_GZIPDecompressorInputStream.cpp" +#include "zip/juce_GZIPCompressorOutputStream.cpp" +#include "zip/juce_ZipFile.cpp" + +//============================================================================== +#if JUCE_MAC || JUCE_IOS +#include "native/juce_osx_ObjCHelpers.h" +#endif + +#if JUCE_ANDROID +#include "native/juce_android_JNIHelpers.h" +#endif + +#if ! JUCE_WINDOWS +#include "native/juce_posix_SharedCode.h" +#include "native/juce_posix_NamedPipe.cpp" +#endif + +//============================================================================== +#if JUCE_MAC || JUCE_IOS +#include "native/juce_mac_Files.mm" +#include "native/juce_mac_Network.mm" +#include "native/juce_mac_Strings.mm" +#include "native/juce_mac_SystemStats.mm" +#include "native/juce_mac_Threads.mm" + +//============================================================================== +#elif JUCE_WINDOWS +#include "native/juce_win32_ComSmartPtr.h" +#include "native/juce_win32_Files.cpp" +#include "native/juce_win32_Network.cpp" +#include "native/juce_win32_Registry.cpp" +#include "native/juce_win32_SystemStats.cpp" +#include "native/juce_win32_Threads.cpp" + +//============================================================================== +#elif JUCE_LINUX +#include "native/juce_linux_Files.cpp" +#include "native/juce_linux_Network.cpp" +#include "native/juce_linux_SystemStats.cpp" +#include "native/juce_linux_Threads.cpp" + +//============================================================================== +#elif JUCE_ANDROID +#include "native/juce_android_Files.cpp" +#include "native/juce_android_Misc.cpp" +#include "native/juce_android_Network.cpp" +#include "native/juce_android_SystemStats.cpp" +#include "native/juce_android_Threads.cpp" + +#endif + +#include "threads/juce_HighResolutionTimer.cpp" + +} diff --git a/source/modules/juce_core/juce_core.h b/source/modules/juce_core/juce_core.h new file mode 100644 index 000000000..f7af33bbe --- /dev/null +++ b/source/modules/juce_core/juce_core.h @@ -0,0 +1,253 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CORE_H_INCLUDED +#define JUCE_CORE_H_INCLUDED + +#ifndef JUCE_MODULE_AVAILABLE_juce_core + /* If you fail to make sure that all your compile units are building JUCE 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 Introjucer-generated AppConfig.h + file before any juce headers. + + Note that if you do have an AppConfig.h file and hit this warning, it means that it doesn't + contain the JUCE_MODULE_AVAILABLE_xxx flags, which are necessary for some inter-module + functionality to work correctly. In that case, you should either rebuild your AppConfig.h with + the latest introjucer, or fix it manually to contain these flags. + */ + #ifdef _MSC_VER + #pragma message ("Have you included your AppConfig.h file before including the JUCE headers?") + #else + #warning "Have you included your AppConfig.h file before including the JUCE headers?" + #endif +#endif + +//============================================================================== +#include "system/juce_TargetPlatform.h" + +//============================================================================= +/** Config: JUCE_FORCE_DEBUG + + Normally, JUCE_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 JUCE_FORCE_DEBUG + //#define JUCE_FORCE_DEBUG 0 +#endif + +//============================================================================= +/** Config: JUCE_LOG_ASSERTIONS + + If this flag is enabled, the the jassert 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 jassert and jassertfalse macros will not be compiled in a + release build. + + @see jassert, jassertfalse, Logger +*/ +#ifndef JUCE_LOG_ASSERTIONS + #if JUCE_ANDROID + #define JUCE_LOG_ASSERTIONS 1 + #else + #define JUCE_LOG_ASSERTIONS 0 + #endif +#endif + +//============================================================================= +/** Config: JUCE_CHECK_MEMORY_LEAKS + + Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector + class and the JUCE_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes. +*/ +#if JUCE_DEBUG && ! defined (JUCE_CHECK_MEMORY_LEAKS) + #define JUCE_CHECK_MEMORY_LEAKS 1 +#endif + +//============================================================================= +/** Config: JUCE_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 JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 +#endif + +/** Config: JUCE_INCLUDE_ZLIB_CODE + This can be used to disable Juce'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 JUCE_ZLIB_INCLUDE_PATH, to + specify the path where your zlib headers live. +*/ +#ifndef JUCE_INCLUDE_ZLIB_CODE + #define JUCE_INCLUDE_ZLIB_CODE 1 +#endif + +#ifndef JUCE_ZLIB_INCLUDE_PATH + #define JUCE_ZLIB_INCLUDE_PATH +#endif + +/* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS + If enabled, this will add some exception-catching code to forward unhandled exceptions + to your JUCEApplication::unhandledException() callback. +*/ +#ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS + //#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 +#endif + +//============================================================================= +//============================================================================= +#if JUCE_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/juce_StandardHeader.h" + +namespace juce +{ + +// START_AUTOINCLUDE containers, files, json, logging, maths, memory, misc, network, +// streams, system, text, threads, time, unit_tests, xml, zip +#include "containers/juce_AbstractFifo.h" +#include "containers/juce_Array.h" +#include "containers/juce_ArrayAllocationBase.h" +#include "containers/juce_DynamicObject.h" +#include "containers/juce_ElementComparator.h" +#include "containers/juce_HashMap.h" +#include "containers/juce_LinkedListPointer.h" +#include "containers/juce_NamedValueSet.h" +#include "containers/juce_OwnedArray.h" +#include "containers/juce_PropertySet.h" +#include "containers/juce_ReferenceCountedArray.h" +#include "containers/juce_ScopedValueSetter.h" +#include "containers/juce_SortedSet.h" +#include "containers/juce_SparseSet.h" +#include "containers/juce_Variant.h" +#include "files/juce_DirectoryIterator.h" +#include "files/juce_File.h" +#include "files/juce_FileInputStream.h" +#include "files/juce_FileOutputStream.h" +#include "files/juce_FileSearchPath.h" +#include "files/juce_MemoryMappedFile.h" +#include "files/juce_TemporaryFile.h" +#include "json/juce_JSON.h" +#include "logging/juce_FileLogger.h" +#include "logging/juce_Logger.h" +#include "maths/juce_BigInteger.h" +#include "maths/juce_Expression.h" +#include "maths/juce_MathsFunctions.h" +#include "maths/juce_Random.h" +#include "maths/juce_Range.h" +#include "memory/juce_Atomic.h" +#include "memory/juce_ByteOrder.h" +#include "memory/juce_HeapBlock.h" +#include "memory/juce_LeakedObjectDetector.h" +#include "memory/juce_Memory.h" +#include "memory/juce_MemoryBlock.h" +#include "memory/juce_OptionalScopedPointer.h" +#include "memory/juce_ReferenceCountedObject.h" +#include "memory/juce_ScopedPointer.h" +#include "memory/juce_Singleton.h" +#include "memory/juce_WeakReference.h" +#include "misc/juce_Result.h" +#include "misc/juce_Uuid.h" +#include "misc/juce_WindowsRegistry.h" +#include "network/juce_IPAddress.h" +#include "network/juce_MACAddress.h" +#include "network/juce_NamedPipe.h" +#include "network/juce_Socket.h" +#include "network/juce_URL.h" +#include "streams/juce_BufferedInputStream.h" +#include "streams/juce_FileInputSource.h" +#include "streams/juce_InputSource.h" +#include "streams/juce_InputStream.h" +#include "streams/juce_MemoryInputStream.h" +#include "streams/juce_MemoryOutputStream.h" +#include "streams/juce_OutputStream.h" +#include "streams/juce_SubregionStream.h" +#include "system/juce_PlatformDefs.h" +#include "system/juce_StandardHeader.h" +#include "system/juce_SystemStats.h" +#include "system/juce_TargetPlatform.h" +#include "text/juce_CharacterFunctions.h" +#include "text/juce_CharPointer_ASCII.h" +#include "text/juce_CharPointer_UTF16.h" +#include "text/juce_CharPointer_UTF32.h" +#include "text/juce_CharPointer_UTF8.h" +#include "text/juce_Identifier.h" +#include "text/juce_LocalisedStrings.h" +#include "text/juce_NewLine.h" +#include "text/juce_String.h" +#include "text/juce_StringArray.h" +#include "text/juce_StringPairArray.h" +#include "text/juce_StringPool.h" +#include "text/juce_TextDiff.h" +#include "threads/juce_ChildProcess.h" +#include "threads/juce_CriticalSection.h" +#include "threads/juce_DynamicLibrary.h" +#include "threads/juce_HighResolutionTimer.h" +#include "threads/juce_InterProcessLock.h" +#include "threads/juce_Process.h" +#include "threads/juce_ReadWriteLock.h" +#include "threads/juce_ScopedLock.h" +#include "threads/juce_ScopedReadLock.h" +#include "threads/juce_ScopedWriteLock.h" +#include "threads/juce_SpinLock.h" +#include "threads/juce_Thread.h" +#include "threads/juce_ThreadLocalValue.h" +#include "threads/juce_ThreadPool.h" +#include "threads/juce_TimeSliceThread.h" +#include "threads/juce_WaitableEvent.h" +#include "time/juce_PerformanceCounter.h" +#include "time/juce_RelativeTime.h" +#include "time/juce_Time.h" +#include "unit_tests/juce_UnitTest.h" +#include "xml/juce_XmlDocument.h" +#include "xml/juce_XmlElement.h" +#include "zip/juce_GZIPCompressorOutputStream.h" +#include "zip/juce_GZIPDecompressorInputStream.h" +#include "zip/juce_ZipFile.h" +// END_AUTOINCLUDE + +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_CORE_H_INCLUDED diff --git a/source/modules/juce_core/juce_core.mm b/source/modules/juce_core/juce_core.mm new file mode 100644 index 000000000..90a2f7cdb --- /dev/null +++ b/source/modules/juce_core/juce_core.mm @@ -0,0 +1,29 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#include "juce_core.cpp" diff --git a/source/modules/juce_core/juce_module_info b/source/modules/juce_core/juce_module_info new file mode 100644 index 000000000..750daaf97 --- /dev/null +++ b/source/modules/juce_core/juce_module_info @@ -0,0 +1,38 @@ +{ + "id": "juce_core", + "name": "JUCE core classes", + "version": "2.1.2", + "description": "The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality.", + "website": "http://www.juce.com/juce", + "license": "ISC Permissive", + + "dependencies": [], + + "include": "juce_core.h", + + "compile": [ { "file": "juce_core.cpp", "target": "! xcode" }, + { "file": "juce_core.mm", "target": "xcode" } ], + + "browse": [ "text/*", + "maths/*", + "memory/*", + "containers/*", + "threads/*", + "time/*", + "files/*", + "network/*", + "streams/*", + "logging/*", + "system/*", + "xml/*", + "json/*", + "zip/*", + "unit_tests/*", + "misc/*", + "native/*" ], + + "OSXFrameworks": "Cocoa IOKit", + "iOSFrameworks": "Foundation", + "LinuxLibs": "rt dl pthread", + "mingwLibs": "uuid wsock32 wininet version ole32 ws2_32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm" +} diff --git a/source/modules/juce_core/logging/juce_FileLogger.cpp b/source/modules/juce_core/logging/juce_FileLogger.cpp new file mode 100644 index 000000000..8cadfda8b --- /dev/null +++ b/source/modules/juce_core/logging/juce_FileLogger.cpp @@ -0,0 +1,134 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_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/source/modules/juce_core/logging/juce_FileLogger.h b/source/modules/juce_core/logging/juce_FileLogger.h new file mode 100644 index 000000000..87cb7cdfc --- /dev/null +++ b/source/modules/juce_core/logging/juce_FileLogger.h @@ -0,0 +1,139 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILELOGGER_H_INCLUDED +#define JUCE_FILELOGGER_H_INCLUDED + +#include "juce_Logger.h" +#include "../files/juce_File.h" +#include "../memory/juce_ScopedPointer.h" + + +//============================================================================== +/** + A simple implementation of a Logger that writes to a file. + + @see Logger +*/ +class JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileLogger) +}; + + +#endif // JUCE_FILELOGGER_H_INCLUDED diff --git a/source/modules/juce_core/logging/juce_Logger.cpp b/source/modules/juce_core/logging/juce_Logger.cpp new file mode 100644 index 000000000..0b1c8d6be --- /dev/null +++ b/source/modules/juce_core/logging/juce_Logger.cpp @@ -0,0 +1,63 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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. + jassert (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 JUCE_LOG_ASSERTIONS || JUCE_DEBUG +void JUCE_API JUCE_CALLTYPE logAssertion (const char* const filename, const int lineNum) noexcept +{ + String m ("JUCE Assertion failure in "); + m << File::createFileWithoutCheckingPath (filename).getFileName() << ':' << lineNum; + + #if JUCE_LOG_ASSERTIONS + Logger::writeToLog (m); + #else + DBG (m); + #endif +} +#endif diff --git a/source/modules/juce_core/logging/juce_Logger.h b/source/modules/juce_core/logging/juce_Logger.h new file mode 100644 index 000000000..142ed338c --- /dev/null +++ b/source/modules/juce_core/logging/juce_Logger.h @@ -0,0 +1,99 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LOGGER_H_INCLUDED +#define JUCE_LOGGER_H_INCLUDED + +#include "../text/juce_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 JUCE_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 JUCE_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 JUCE_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 + juce_PlatformDefs.h (which will avoid calling the method in non-debug builds). + */ + static void JUCE_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 // JUCE_LOGGER_H_INCLUDED diff --git a/source/modules/juce_core/maths/juce_BigInteger.cpp b/source/modules/juce_core/maths/juce_BigInteger.cpp new file mode 100644 index 000000000..398fbc354 --- /dev/null +++ b/source/modules/juce_core/maths/juce_BigInteger.cpp @@ -0,0 +1,1021 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) jmax ((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 JUCE_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(); + jassert (other.numValues >= 4); + numValues = (size_t) jmax ((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 = jmin (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) jmin (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 = jmin (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 JUCE_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 + { + jassert (n != 0); // (the built-in functions may not work for n = 0) + + #if JUCE_GCC + return 31 - __builtin_clz (n); + #elif JUCE_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; + } + + jassert (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) +{ + jassert (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.. + jassert (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.. + jassert (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 + jassert (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& JUCE_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 ((juce_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 juce_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 juce_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/source/modules/juce_core/maths/juce_BigInteger.h b/source/modules/juce_core/maths/juce_BigInteger.h new file mode 100644 index 000000000..abf48a224 --- /dev/null +++ b/source/modules/juce_core/maths/juce_BigInteger.h @@ -0,0 +1,334 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BIGINTEGER_H_INCLUDED +#define JUCE_BIGINTEGER_H_INCLUDED + +#include "../text/juce_String.h" +#include "../memory/juce_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 JUCE_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 JUCE_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); + + JUCE_LEAK_DETECTOR (BigInteger) +}; + +/** Writes a BigInteger to an OutputStream as a UTF8 decimal string. */ +OutputStream& JUCE_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 // JUCE_BIGINTEGER_H_INCLUDED diff --git a/source/modules/juce_core/maths/juce_Expression.cpp b/source/modules/juce_core/maths/juce_Expression.cpp new file mode 100644 index 000000000..41b144a8c --- /dev/null +++ b/source/modules/juce_core/maths/juce_Expression.cpp @@ -0,0 +1,1185 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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: + JUCE_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) + { + jassert (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 + { + jassert (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: + JUCE_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; + + JUCE_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; + + JUCE_DECLARE_NON_COPYABLE (SymbolRenamingVisitor) + }; + + SymbolTerm* getSymbol() const { return static_cast (left.get()); } + + JUCE_DECLARE_NON_COPYABLE (DotOperator) + }; + + //============================================================================== + class Negate : public Term + { + public: + explicit Negate (const TermPtr& t) : input (t) + { + jassert (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; + jassert (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: + JUCE_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: + JUCE_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: + JUCE_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: + JUCE_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) + { + jassert (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; + + JUCE_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; + + JUCE_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 juce_wchar c) noexcept + { + return c >= '0' && c <= '9'; + } + + bool readChar (const juce_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 ((juce_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 ((juce_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 ((juce_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 ((juce_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.. + jassert (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; + } + + JUCE_DECLARE_NON_COPYABLE (Parser) + }; +}; + +//============================================================================== +Expression::Expression() + : term (new Expression::Helpers::Constant (0, false)) +{ +} + +Expression::~Expression() +{ +} + +Expression::Expression (Term* const term_) + : term (term_) +{ + jassert (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 JUCE_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); + } + + jassert (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 +{ + jassert (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 = jmin (v, parameters[i]); + + return v; + } + + if (functionName == "max") + { + double v = parameters[0]; + for (int i = 1; i < numParams; ++i) + v = jmax (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/source/modules/juce_core/maths/juce_Expression.h b/source/modules/juce_core/maths/juce_Expression.h new file mode 100644 index 000000000..3b08bef62 --- /dev/null +++ b/source/modules/juce_core/maths/juce_Expression.h @@ -0,0 +1,274 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_EXPRESSION_H_INCLUDED +#define JUCE_EXPRESSION_H_INCLUDED + +#include "../memory/juce_ReferenceCountedObject.h" +#include "../containers/juce_Array.h" +#include "../memory/juce_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 JUCE_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 JUCE_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 JUCE_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 // JUCE_EXPRESSION_H_INCLUDED diff --git a/source/modules/juce_core/maths/juce_MathsFunctions.h b/source/modules/juce_core/maths/juce_MathsFunctions.h new file mode 100644 index 000000000..2e008170a --- /dev/null +++ b/source/modules/juce_core/maths/juce_MathsFunctions.h @@ -0,0 +1,514 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MATHSFUNCTIONS_H_INCLUDED +#define JUCE_MATHSFUNCTIONS_H_INCLUDED + +//============================================================================== +/* + 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 JUCE_MSVC + /** A platform-independent 64-bit integer type. */ + typedef __int64 int64; + /** A platform-independent 64-bit unsigned integer type. */ + typedef unsigned __int64 uint64; +#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; +#endif + +#ifndef DOXYGEN + /** A macro for creating 64-bit literals. + Historically, this was needed to support portability with MSVC6, and is kept here + so that old code will still compile, but nowadays every compiler will support the + LL and ULL suffixes, so you should use those in preference to this macro. + */ + #define literal64bit(longLiteral) (longLiteral##LL) +#endif + +#if JUCE_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 JUCE_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 JUCE_MSVC + typedef pointer_sized_int ssize_t; +#endif + +//============================================================================== +// Some indispensible min/max functions + +/** Returns the larger of two values. */ +template +inline Type jmax (const Type a, const Type b) { return (a < b) ? b : a; } + +/** Returns the larger of three values. */ +template +inline Type jmax (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 jmax (const Type a, const Type b, const Type c, const Type d) { return jmax (a, jmax (b, c, d)); } + +/** Returns the smaller of two values. */ +template +inline Type jmin (const Type a, const Type b) { return (b < a) ? b : a; } + +/** Returns the smaller of three values. */ +template +inline Type jmin (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 jmin (const Type a, const Type b, const Type c, const Type d) { return jmin (a, jmin (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 jmax (lowerLimit, jmin (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, jmin, jmax +*/ +template +inline Type jlimit (const Type lowerLimit, + const Type upperLimit, + const Type valueToConstrain) noexcept +{ + jassert (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 +{ + jassert (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 +{ + jassert (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 +{ + jassert (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 +{ + jassert (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 juce_hypot is easier than dealing with the different types of hypot function + that are provided by the various platforms and compilers. */ +template +inline Type juce_hypot (Type a, Type b) noexcept +{ + #if JUCE_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 juce_isfinite (FloatingPointType value) +{ + #if JUCE_WINDOWS + return _finite (value); + #elif JUCE_ANDROID + return isfinite (value); + #else + return std::isfinite (value); + #endif +} + +//============================================================================== +#if JUCE_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 JUCE_BIG_ENDIAN + return n.asInt [1]; + #else + return n.asInt [0]; + #endif +} + +#if JUCE_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 +{ + jassert (divisor > 0); + dividend %= divisor; + return (dividend < 0) ? (dividend + divisor) : dividend; +} + +//============================================================================== +#if (JUCE_INTEL && JUCE_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 JUCE_UNDENORMALISE(x) x += 1.0f; x -= 1.0f; +#else + #define JUCE_UNDENORMALISE(x) +#endif + +//============================================================================== +/** This namespace contains a few template classes for helping work out class type variations. +*/ +namespace TypeHelpers +{ + #if JUCE_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 // JUCE_MATHSFUNCTIONS_H_INCLUDED diff --git a/source/modules/juce_core/maths/juce_Random.cpp b/source/modules/juce_core/maths/juce_Random.cpp new file mode 100644 index 000000000..2f7874bed --- /dev/null +++ b/source/modules/juce_core/maths/juce_Random.cpp @@ -0,0 +1,190 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 * 0x5deece66dLL + 11) & 0xffffffffffffLL; + + return (int) (seed >> 16); +} + +int Random::nextInt (const int maxValue) noexcept +{ + jassert (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 (void* const buffer, size_t bytes) +{ + int* d = static_cast (buffer); + + for (; bytes >= sizeof (int); bytes -= sizeof (int)) + *d++ = nextInt(); + + if (bytes > 0) + { + const int lastBytes = nextInt(); + memcpy (d, &lastBytes, bytes); + } +} + +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 JUCE_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/source/modules/juce_core/maths/juce_Random.h b/source/modules/juce_core/maths/juce_Random.h new file mode 100644 index 000000000..67d6a59cd --- /dev/null +++ b/source/modules/juce_core/maths/juce_Random.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RANDOM_H_INCLUDED +#define JUCE_RANDOM_H_INCLUDED + +#include "juce_BigInteger.h" + + +//============================================================================== +/** + A random number generator. + + You can create a Random object and use it to generate a sequence of random numbers. +*/ +class JUCE_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); + + /** Fills a block of memory with random values. */ + void fillBitsRandomly (void* bufferToFill, size_t sizeInBytes); + + /** 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; + + JUCE_LEAK_DETECTOR (Random) +}; + + +#endif // JUCE_RANDOM_H_INCLUDED diff --git a/source/modules/juce_core/maths/juce_Range.h b/source/modules/juce_core/maths/juce_Range.h new file mode 100644 index 000000000..56678c1b9 --- /dev/null +++ b/source/modules/juce_core/maths/juce_Range.h @@ -0,0 +1,264 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RANGE_H_INCLUDED +#define JUCE_RANGE_H_INCLUDED + + +//============================================================================== +/** 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 (jmax (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, jmax (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 (jmin (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 + jmax (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 jlimit (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 (jmax (start, other.start), + jmin (end, other.end)); + } + + /** Returns the smallest range that contains both this one and the other one. */ + Range getUnionWith (Range other) const noexcept + { + return Range (jmin (start, other.start), + jmax (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 (jlimit (start, end - otherLen, rangeToConstrain.getStart())); + } + +private: + //============================================================================== + ValueType start, end; +}; + + +#endif // JUCE_RANGE_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_Atomic.h b/source/modules/juce_core/memory/juce_Atomic.h new file mode 100644 index 000000000..1de423743 --- /dev/null +++ b/source/modules/juce_core/memory/juce_Atomic.h @@ -0,0 +1,396 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ATOMIC_H_INCLUDED +#define JUCE_ATOMIC_H_INCLUDED + + +//============================================================================== +/** + 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 explicit 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_jassert (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 JUCE_64BIT + JUCE_ALIGN (8) + #else + JUCE_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: + template + static inline Dest castTo (Source value) noexcept { union { Dest d; Source s; } u; u.s = value; return u.d; } + + static inline Type castFrom32Bit (int32 value) noexcept { return castTo (value); } + static inline Type castFrom64Bit (int64 value) noexcept { return castTo (value); } + static inline int32 castTo32Bit (Type value) noexcept { return castTo (value); } + static inline int64 castTo64Bit (Type value) noexcept { return castTo (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 JUCE_IOS || (JUCE_MAC && (JUCE_PPC || JUCE_CLANG || __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2))) + #define JUCE_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 JUCE_MAC_ATOMICS_VOLATILE + #else + #define JUCE_MAC_ATOMICS_VOLATILE volatile + #endif + + #if JUCE_PPC || JUCE_IOS + // None of these atomics are available for PPC or for iOS 3.1 or earlier!! + template static Type OSAtomicAdd64Barrier (Type b, JUCE_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return *a += b; } + template static Type OSAtomicIncrement64Barrier (JUCE_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return ++*a; } + template static Type OSAtomicDecrement64Barrier (JUCE_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return --*a; } + template static bool OSAtomicCompareAndSwap64Barrier (Type old, Type newValue, JUCE_MAC_ATOMICS_VOLATILE Type* value) noexcept + { jassertfalse; if (old == *value) { *value = newValue; return true; } return false; } + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 + #endif + +//============================================================================== +#elif JUCE_GCC + #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics + + #if JUCE_IOS || JUCE_ANDROID // (64-bit ops will compile but not link on these mobile OSes) + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 + #endif + +//============================================================================== +#else + #define JUCE_ATOMICS_WINDOWS 1 // Windows with intrinsics + + #if JUCE_USE_INTRINSICS + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchange, _InterlockedIncrement, _InterlockedDecrement, _InterlockedCompareExchange, \ + _InterlockedCompareExchange64, _InterlockedExchangeAdd, _ReadWriteBarrier) + #endif + #define juce_InterlockedExchange(a, b) _InterlockedExchange(a, b) + #define juce_InterlockedIncrement(a) _InterlockedIncrement(a) + #define juce_InterlockedDecrement(a) _InterlockedDecrement(a) + #define juce_InterlockedExchangeAdd(a, b) _InterlockedExchangeAdd(a, b) + #define juce_InterlockedCompareExchange(a, b, c) _InterlockedCompareExchange(a, b, c) + #define juce_InterlockedCompareExchange64(a, b, c) _InterlockedCompareExchange64(a, b, c) + #define juce_MemoryBarrier _ReadWriteBarrier + #else + long juce_InterlockedExchange (volatile long* a, long b) noexcept; + long juce_InterlockedIncrement (volatile long* a) noexcept; + long juce_InterlockedDecrement (volatile long* a) noexcept; + long juce_InterlockedExchangeAdd (volatile long* a, long b) noexcept; + long juce_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept; + __int64 juce_InterlockedCompareExchange64 (volatile __int64* a, __int64 b, __int64 c) noexcept; + inline void juce_MemoryBarrier() noexcept { long x = 0; juce_InterlockedIncrement (&x); } + #endif + + #if JUCE_64BIT + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchangeAdd64, _InterlockedExchange64, _InterlockedIncrement64, _InterlockedDecrement64) + #endif + #define juce_InterlockedExchangeAdd64(a, b) _InterlockedExchangeAdd64(a, b) + #define juce_InterlockedExchange64(a, b) _InterlockedExchange64(a, b) + #define juce_InterlockedIncrement64(a) _InterlockedIncrement64(a) + #define juce_InterlockedDecrement64(a) _InterlockedDecrement64(a) + #else + // None of these atomics are available in a 32-bit Windows build!! + template static Type juce_InterlockedExchangeAdd64 (volatile Type* a, Type b) noexcept { jassertfalse; Type old = *a; *a += b; return old; } + template static Type juce_InterlockedExchange64 (volatile Type* a, Type b) noexcept { jassertfalse; Type old = *a; *a = b; return old; } + template static Type juce_InterlockedIncrement64 (volatile Type* a) noexcept { jassertfalse; return ++*a; } + template static Type juce_InterlockedDecrement64 (volatile Type* a) noexcept { jassertfalse; return --*a; } + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 + #endif +#endif + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4311) // (truncation warning) +#endif + +//============================================================================== +template +inline Type Atomic::get() const noexcept +{ + #if JUCE_ATOMICS_MAC + return sizeof (Type) == 4 ? castFrom32Bit ((int32) OSAtomicAdd32Barrier ((int32_t) 0, (JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value)) + : castFrom64Bit ((int64) OSAtomicAdd64Barrier ((int64_t) 0, (JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value)); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) juce_InterlockedExchangeAdd ((volatile long*) &value, (long) 0)) + : castFrom64Bit ((int64) juce_InterlockedExchangeAdd64 ((volatile __int64*) &value, (__int64) 0)); + #elif JUCE_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 JUCE_ATOMICS_MAC || JUCE_ATOMICS_GCC + Type currentVal = value; + while (! compareAndSetBool (newValue, currentVal)) { currentVal = value; } + return currentVal; + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) juce_InterlockedExchange ((volatile long*) &value, (long) castTo32Bit (newValue))) + : castFrom64Bit ((int64) juce_InterlockedExchange64 ((volatile __int64*) &value, (__int64) castTo64Bit (newValue))); + #endif +} + +template +inline Type Atomic::operator+= (const Type amountToAdd) noexcept +{ + #if JUCE_ATOMICS_MAC + return sizeof (Type) == 4 ? (Type) OSAtomicAdd32Barrier ((int32_t) castTo32Bit (amountToAdd), (JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicAdd64Barrier ((int64_t) amountToAdd, (JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) (juce_InterlockedExchangeAdd ((volatile long*) &value, (long) amountToAdd) + (long) amountToAdd) + : (Type) (juce_InterlockedExchangeAdd64 ((volatile __int64*) &value, (__int64) amountToAdd) + (__int64) amountToAdd); + #elif JUCE_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 JUCE_ATOMICS_MAC + return sizeof (Type) == 4 ? (Type) OSAtomicIncrement32Barrier ((JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicIncrement64Barrier ((JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) juce_InterlockedIncrement ((volatile long*) &value) + : (Type) juce_InterlockedIncrement64 ((volatile __int64*) &value); + #elif JUCE_ATOMICS_GCC + return (Type) __sync_add_and_fetch (&value, 1); + #endif +} + +template +inline Type Atomic::operator--() noexcept +{ + #if JUCE_ATOMICS_MAC + return sizeof (Type) == 4 ? (Type) OSAtomicDecrement32Barrier ((JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicDecrement64Barrier ((JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) juce_InterlockedDecrement ((volatile long*) &value) + : (Type) juce_InterlockedDecrement64 ((volatile __int64*) &value); + #elif JUCE_ATOMICS_GCC + return (Type) __sync_add_and_fetch (&value, -1); + #endif +} + +template +inline bool Atomic::compareAndSetBool (const Type newValue, const Type valueToCompare) noexcept +{ + #if JUCE_ATOMICS_MAC + return sizeof (Type) == 4 ? OSAtomicCompareAndSwap32Barrier ((int32_t) castTo32Bit (valueToCompare), (int32_t) castTo32Bit (newValue), (JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : OSAtomicCompareAndSwap64Barrier ((int64_t) castTo64Bit (valueToCompare), (int64_t) castTo64Bit (newValue), (JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return compareAndSetValue (newValue, valueToCompare) == valueToCompare; + #elif JUCE_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 JUCE_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 JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) juce_InterlockedCompareExchange ((volatile long*) &value, (long) castTo32Bit (newValue), (long) castTo32Bit (valueToCompare))) + : castFrom64Bit ((int64) juce_InterlockedCompareExchange64 ((volatile __int64*) &value, (__int64) castTo64Bit (newValue), (__int64) castTo64Bit (valueToCompare))); + #elif JUCE_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 JUCE_ATOMICS_MAC + OSMemoryBarrier(); + #elif JUCE_ATOMICS_GCC + __sync_synchronize(); + #elif JUCE_ATOMICS_WINDOWS + juce_MemoryBarrier(); + #endif +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_ATOMIC_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_ByteOrder.h b/source/modules/juce_core/memory/juce_ByteOrder.h new file mode 100644 index 000000000..fedbeac7b --- /dev/null +++ b/source/modules/juce_core/memory/juce_ByteOrder.h @@ -0,0 +1,186 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BYTEORDER_H_INCLUDED +#define JUCE_BYTEORDER_H_INCLUDED + + +//============================================================================== +/** Contains static methods for converting the byte order between different + endiannesses. +*/ +class JUCE_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(); + + JUCE_DECLARE_NON_COPYABLE (ByteOrder) +}; + + +//============================================================================== +#if JUCE_USE_INTRINSICS && ! defined (__INTEL_COMPILER) + #pragma intrinsic (_byteswap_ulong) +#endif + +inline uint16 ByteOrder::swap (uint16 n) +{ + #if JUCE_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 JUCE_MAC || JUCE_IOS + return OSSwapInt32 (n); + #elif JUCE_GCC && JUCE_INTEL && ! JUCE_NO_INLINE_ASM + asm("bswap %%eax" : "=a"(n) : "a"(n)); + return n; + #elif JUCE_USE_INTRINSICS + return _byteswap_ulong (n); + #elif JUCE_MSVC && ! JUCE_NO_INLINE_ASM + __asm { + mov eax, n + bswap eax + mov n, eax + } + return n; + #elif JUCE_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 JUCE_MAC || JUCE_IOS + return OSSwapInt64 (value); + #elif JUCE_USE_INTRINSICS + return _byteswap_uint64 (value); + #else + return (((int64) swap ((uint32) value)) << 32) | swap ((uint32) (value >> 32)); + #endif +} + +#if JUCE_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 // JUCE_BYTEORDER_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_HeapBlock.h b/source/modules/juce_core/memory/juce_HeapBlock.h new file mode 100644 index 000000000..3731f0b30 --- /dev/null +++ b/source/modules/juce_core/memory/juce_HeapBlock.h @@ -0,0 +1,308 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_HEAPBLOCK_H_INCLUDED +#define JUCE_HEAPBLOCK_H_INCLUDED + +#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 JUCE_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 (JUCE_DLL) || defined (JUCE_DLL_BUILD)) + JUCE_DECLARE_NON_COPYABLE (HeapBlock) + JUCE_PREVENT_HEAP_ALLOCATION // Creating a 'new HeapBlock' would be missing the point! + #endif +}; + + +#endif // JUCE_HEAPBLOCK_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_LeakedObjectDetector.h b/source/modules/juce_core/memory/juce_LeakedObjectDetector.h new file mode 100644 index 000000000..56e4a670c --- /dev/null +++ b/source/modules/juce_core/memory/juce_LeakedObjectDetector.h @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED +#define JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED + +#include "../text/juce_String.h" +#include "juce_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 JUCE_LEAK_DETECTOR macro as a simple way to put one in your + class declaration. Have a look through the juce 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 (JUCE_LEAK_DETECTOR) + #if (DOXYGEN || JUCE_CHECK_MEMORY_LEAKS) + /** This macro lets you embed a leak-detecting object inside a class. + + To use it, simply declare a JUCE_LEAK_DETECTOR(YourClassName) inside a private section + of the class declaration. E.g. + + @code + class MyClass + { + public: + MyClass(); + void blahBlah(); + + private: + JUCE_LEAK_DETECTOR (MyClass) + }; + @endcode + + @see JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR, LeakedObjectDetector + */ + #define JUCE_LEAK_DETECTOR(OwnerClass) \ + friend class juce::LeakedObjectDetector; \ + static const char* getLeakedObjectClassName() noexcept { return #OwnerClass; } \ + juce::LeakedObjectDetector JUCE_JOIN_MACRO (leakDetector, __LINE__); + #else + #define JUCE_LEAK_DETECTOR(OwnerClass) + #endif +#endif + + +#endif // JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_Memory.h b/source/modules/juce_core/memory/juce_Memory.h new file mode 100644 index 000000000..57d1644dc --- /dev/null +++ b/source/modules/juce_core/memory/juce_Memory.h @@ -0,0 +1,126 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORY_H_INCLUDED +#define JUCE_MEMORY_H_INCLUDED + +//============================================================================== +/** 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 JUCE_MAC || JUCE_IOS || DOXYGEN + + /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. + You should use the JUCE_AUTORELEASEPOOL macro to create a local auto-release pool on the stack. + */ + class JUCE_API ScopedAutoReleasePool + { + public: + ScopedAutoReleasePool(); + ~ScopedAutoReleasePool(); + + private: + void* pool; + + JUCE_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 (JUCE_COMPILER_SUPPORTS_ARC && defined (__OBJC__)) || DOXYGEN + #define JUCE_AUTORELEASEPOOL @autoreleasepool +#else + #define JUCE_AUTORELEASEPOOL const juce::ScopedAutoReleasePool JUCE_JOIN_MACRO (autoReleasePool_, __LINE__); +#endif + +#else + #define JUCE_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 juce 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 JUCE_LEAK_DETECTOR macro, these allocators can be injected into most juce classes. +*/ +#if JUCE_MSVC && (defined (JUCE_DLL) || defined (JUCE_DLL_BUILD)) && ! (JUCE_DISABLE_DLL_ALLOCATORS || DOXYGEN) + extern JUCE_API void* juceDLL_malloc (size_t); + extern JUCE_API void juceDLL_free (void*); + + #define JUCE_LEAK_DETECTOR(OwnerClass) public:\ + static void* operator new (size_t sz) { return juce::juceDLL_malloc (sz); } \ + static void* operator new (size_t, void* p) { return p; } \ + static void operator delete (void* p) { juce::juceDLL_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 JUCE_LEAK_DETECTOR instead. +*/ +#ifndef juce_UseDebuggingNewOperator + #define juce_UseDebuggingNewOperator +#endif + + +#endif // JUCE_MEMORY_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_MemoryBlock.cpp b/source/modules/juce_core/memory/juce_MemoryBlock.cpp new file mode 100644 index 000000000..29f930798 --- /dev/null +++ b/source/modules/juce_core/memory/juce_MemoryBlock.cpp @@ -0,0 +1,421 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) + { + jassert (other.data != nullptr); + data.malloc (size); + memcpy (data, other.data, size); + } +} + +MemoryBlock::MemoryBlock (const void* const dataToInitialiseFrom, const size_t sizeInBytes) + : size (sizeInBytes) +{ + jassert (((ssize_t) sizeInBytes) >= 0); + + if (size > 0) + { + jassert (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 JUCE_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) + { + jassert (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) + { + jassert (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) + { + jassert (srcData != nullptr); // this must not be null! + insertPosition = jmin (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 = jmin ((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 = jmin ((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 juce_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 ((juce_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/source/modules/juce_core/memory/juce_MemoryBlock.h b/source/modules/juce_core/memory/juce_MemoryBlock.h new file mode 100644 index 000000000..b5adcab94 --- /dev/null +++ b/source/modules/juce_core/memory/juce_MemoryBlock.h @@ -0,0 +1,260 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYBLOCK_H_INCLUDED +#define JUCE_MEMORYBLOCK_H_INCLUDED + +#include "../text/juce_String.h" +#include "../memory/juce_HeapBlock.h" + + +//============================================================================== +/** + A class to hold a resizable block of raw data. + +*/ +class JUCE_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 JUCE_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; + + JUCE_LEAK_DETECTOR (MemoryBlock) +}; + + +#endif // JUCE_MEMORYBLOCK_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_OptionalScopedPointer.h b/source/modules/juce_core/memory/juce_OptionalScopedPointer.h new file mode 100644 index 000000000..cc5531ea0 --- /dev/null +++ b/source/modules/juce_core/memory/juce_OptionalScopedPointer.h @@ -0,0 +1,188 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OPTIONALSCOPEDPOINTER_H_INCLUDED +#define JUCE_OPTIONALSCOPEDPOINTER_H_INCLUDED + +#include "juce_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 // JUCE_OPTIONALSCOPEDPOINTER_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_ReferenceCountedObject.h b/source/modules/juce_core/memory/juce_ReferenceCountedObject.h new file mode 100644 index 000000000..751a45fee --- /dev/null +++ b/source/modules/juce_core/memory/juce_ReferenceCountedObject.h @@ -0,0 +1,400 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED +#define JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED + +#include "juce_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 JUCE_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 + { + jassert (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! + jassert (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; + + JUCE_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 JUCE_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 + { + jassert (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! + jassert (getReferenceCount() == 0); + } + +private: + //============================================================================== + int refCount; + + JUCE_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 JUCE_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 JUCE_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 // JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_ScopedPointer.h b/source/modules/juce_core/memory/juce_ScopedPointer.h new file mode 100644 index 000000000..7f9580411 --- /dev/null +++ b/source/modules/juce_core/memory/juce_ScopedPointer.h @@ -0,0 +1,253 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDPOINTER_H_INCLUDED +#define JUCE_SCOPEDPOINTER_H_INCLUDED + +//============================================================================== +/** + 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: + //============================================================================== + /** Creates a ScopedPointer containing a null pointer. */ + inline ScopedPointer() noexcept : object (nullptr) + { + } + + /** Creates a ScopedPointer that owns the specified object. */ + inline ScopedPointer (ObjectType* const objectToTakePossessionOf) noexcept + : object (objectToTakePossessionOf) + { + } + + /** 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 = 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! + jassert (object == nullptr || object != objectToTransferFrom.object); + + ObjectType* const oldObject = object; + object = objectToTransferFrom.object; + 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) + { + ObjectType* const oldObject = object; + object = newObjectToTakePossessionOf; + delete oldObject; + } + + return *this; + } + + #if JUCE_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! + jassert (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; + + // (Required as an alternative to the overloaded & operator). + const ScopedPointer* getAddress() const noexcept { return this; } + + #if ! JUCE_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. + */ + JUCE_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) 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) 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_jassert (sizeof (Type) == 12345); } +#endif + +#endif // JUCE_SCOPEDPOINTER_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_Singleton.h b/source/modules/juce_core/memory/juce_Singleton.h new file mode 100644 index 000000000..00ea0afe0 --- /dev/null +++ b/source/modules/juce_core/memory/juce_Singleton.h @@ -0,0 +1,292 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SINGLETON_H_INCLUDED +#define JUCE_SINGLETON_H_INCLUDED + + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + To use this, add the line juce_DeclareSingleton (MyClass, doNotRecreateAfterDeletion) + to the class's definition. + + Then put a macro juce_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(); + } + + juce_DeclareSingleton (MySingleton, false) + }; + + juce_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 juce_DeclareSingleton_SingleThreaded() macro instead + of this one. + + @see juce_ImplementSingleton, juce_DeclareSingleton_SingleThreaded +*/ +#define juce_DeclareSingleton(classname, doNotRecreateAfterDeletion) \ +\ + static classname* _singletonInstance; \ + static juce::CriticalSection _singletonLock; \ +\ + static classname* JUCE_CALLTYPE getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + {\ + const juce::ScopedLock sl (_singletonLock); \ +\ + if (_singletonInstance == nullptr) \ + { \ + static bool alreadyInside = false; \ + static bool createdOnceAlready = false; \ +\ + const bool problem = alreadyInside || ((doNotRecreateAfterDeletion) && createdOnceAlready); \ + jassert (! 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* JUCE_CALLTYPE getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void JUCE_CALLTYPE deleteInstance() \ + { \ + const juce::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 juce_DeclareSingleton macro. + + After adding the juce_DeclareSingleton to the class definition, this macro has + to be used in the cpp file. +*/ +#define juce_ImplementSingleton(classname) \ +\ + classname* classname::_singletonInstance = nullptr; \ + juce::CriticalSection classname::_singletonLock; + + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + This is exactly the same as juce_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 juce_DeclareSingleton for more information about + how to use it, the only difference being that you have to use + juce_ImplementSingleton_SingleThreaded instead of juce_ImplementSingleton. + + @see juce_ImplementSingleton_SingleThreaded, juce_DeclareSingleton, juce_DeclareSingleton_SingleThreaded_Minimal +*/ +#define juce_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); \ + jassert (! 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 juce_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. + + Juce use the normal juce_ImplementSingleton_SingleThreaded as the counterpart + to this declaration, as you would with juce_DeclareSingleton_SingleThreaded. + + See the documentation for juce_DeclareSingleton for more information about + how to use it, the only difference being that you have to use + juce_ImplementSingleton_SingleThreaded instead of juce_ImplementSingleton. + + @see juce_ImplementSingleton_SingleThreaded, juce_DeclareSingleton +*/ +#define juce_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 juce_DeclareSingleton_SingleThreaded macro. + + After adding juce_DeclareSingleton_SingleThreaded or juce_DeclareSingleton_SingleThreaded_Minimal + to the class definition, this macro has to be used somewhere in the cpp file. +*/ +#define juce_ImplementSingleton_SingleThreaded(classname) \ +\ + classname* classname::_singletonInstance = nullptr; + + + +#endif // JUCE_SINGLETON_H_INCLUDED diff --git a/source/modules/juce_core/memory/juce_WeakReference.h b/source/modules/juce_core/memory/juce_WeakReference.h new file mode 100644 index 000000000..ac2048a07 --- /dev/null +++ b/source/modules/juce_core/memory/juce_WeakReference.h @@ -0,0 +1,214 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WEAKREFERENCE_H_INCLUDED +#define JUCE_WEAKREFERENCE_H_INCLUDED + +#include "juce_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 JUCE_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; + + JUCE_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. + jassert (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!! + jassert (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; + + JUCE_DECLARE_NON_COPYABLE (Master) + }; + +private: + SharedRef holder; + + static inline SharedPointer* getRef (ObjectType* const o) + { + return (o != nullptr) ? o->masterReference.getSharedPointer (o) : nullptr; + } +}; + + +#endif // JUCE_WEAKREFERENCE_H_INCLUDED diff --git a/source/modules/juce_core/misc/juce_Result.cpp b/source/modules/juce_core/misc/juce_Result.cpp new file mode 100644 index 000000000..1734907f5 --- /dev/null +++ b/source/modules/juce_core/misc/juce_Result.cpp @@ -0,0 +1,83 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +Result::Result() noexcept {} + +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 JUCE_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::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/source/modules/juce_core/misc/juce_Result.h b/source/modules/juce_core/misc/juce_Result.h new file mode 100644 index 000000000..be88580bf --- /dev/null +++ b/source/modules/juce_core/misc/juce_Result.h @@ -0,0 +1,127 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RESULT_H_INCLUDED +#define JUCE_RESULT_H_INCLUDED + +#include "../text/juce_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 JUCE_API Result +{ +public: + //============================================================================== + /** Creates and returns a 'successful' result. */ + static Result ok() noexcept { return Result(); } + + /** 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&); + Result& operator= (const Result&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Result (Result&&) noexcept; + Result& operator= (Result&&) noexcept; + #endif + + bool operator== (const Result& other) const noexcept; + bool operator!= (const Result& other) const noexcept; + +private: + String errorMessage; + + // The default constructor is not for public use! + // Instead, use Result::ok() or Result::fail() + Result() noexcept; + 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 // JUCE_RESULT_H_INCLUDED diff --git a/source/modules/juce_core/misc/juce_Uuid.cpp b/source/modules/juce_core/misc/juce_Uuid.cpp new file mode 100644 index 000000000..9df787f4a --- /dev/null +++ b/source/modules/juce_core/misc/juce_Uuid.cpp @@ -0,0 +1,114 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/misc/juce_Uuid.h b/source/modules/juce_core/misc/juce_Uuid.h new file mode 100644 index 000000000..9358de008 --- /dev/null +++ b/source/modules/juce_core/misc/juce_Uuid.h @@ -0,0 +1,114 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_UUID_H_INCLUDED +#define JUCE_UUID_H_INCLUDED + +#include "../text/juce_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 JUCE_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]; + + JUCE_LEAK_DETECTOR (Uuid) +}; + + +#endif // JUCE_UUID_H_INCLUDED diff --git a/source/modules/juce_core/misc/juce_WindowsRegistry.h b/source/modules/juce_core/misc/juce_WindowsRegistry.h new file mode 100644 index 000000000..47730acd3 --- /dev/null +++ b/source/modules/juce_core/misc/juce_WindowsRegistry.h @@ -0,0 +1,130 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WINDOWSREGISTRY_H_INCLUDED +#define JUCE_WINDOWSREGISTRY_H_INCLUDED + +#if JUCE_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); + + /** Returns true if the given key exists in the registry. */ + static bool keyExists (const String& regValuePath); + + /** Returns true if the given key exists in the registry. */ + static bool keyExistsWow64 (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(); + JUCE_DECLARE_NON_COPYABLE (WindowsRegistry) +}; + +#endif +#endif // JUCE_WINDOWSREGISTRY_H_INCLUDED diff --git a/source/modules/juce_core/native/java/JuceAppActivity.java b/source/modules/juce_core/native/java/JuceAppActivity.java new file mode 100644 index 000000000..8c3aae471 --- /dev/null +++ b/source/modules/juce_core/native/java/JuceAppActivity.java @@ -0,0 +1,707 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +package com.juce; + +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 android.util.DisplayMetrics; +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.AudioManager; +import android.media.MediaScannerConnection; +import android.media.MediaScannerConnection.MediaScannerConnectionClient; + +//============================================================================== +public final class JuceAppActivity extends Activity +{ + //============================================================================== + static + { + System.loadLibrary ("juce_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, int dpi); + + //============================================================================== + 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(), getDPI()); + + 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 final int getDPI() + { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics (metrics); + return metrics.densityDpi; + } + + 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(); + JuceAppActivity.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(); + JuceAppActivity.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.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(); + JuceAppActivity.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("No", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 2); + } + }) + .setNeutralButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.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/source/modules/juce_core/native/juce_BasicNativeHeaders.h b/source/modules/juce_core/native/juce_BasicNativeHeaders.h new file mode 100644 index 000000000..aecfc4042 --- /dev/null +++ b/source/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -0,0 +1,223 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BASICNATIVEHEADERS_H_INCLUDED +#define JUCE_BASICNATIVEHEADERS_H_INCLUDED + +#include "../system/juce_TargetPlatform.h" +#undef T + +//============================================================================== +#if JUCE_MAC || JUCE_IOS + + #if JUCE_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 JUCE_WINDOWS + #if JUCE_MSVC + #ifndef _CPPRTTI + #error "You're compiling without RTTI enabled! This is needed for a lot of JUCE classes, please update your compiler settings!" + #endif + + #ifndef _CPPUNWIND + #error "You're compiling without exceptions enabled! This is needed for a lot of JUCE 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 + #if JUCE_MINGW + #define _WIN32_WINNT 0x0501 + #else + #define _WIN32_WINNT 0x0600 + #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 JUCE_MINGW + #include + #else + #include + #include + #endif + + #undef PACKED + + #if JUCE_MSVC + #pragma warning (pop) + #pragma warning (4: 4511 4512 4100 /*4365*/) // (enable some warnings that are turned off in VC8) + #endif + + #if JUCE_MSVC && ! JUCE_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 JUCE_LOAD_WINAPI_FUNCTION(dll, functionName, localFunctionName, returnType, params) \ + typedef returnType (WINAPI *type##localFunctionName) params; \ + type##localFunctionName localFunctionName = (type##localFunctionName) dll.getFunction (#functionName); + +//============================================================================== +#elif JUCE_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 JUCE_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 // JUCE_BASICNATIVEHEADERS_H_INCLUDED diff --git a/source/modules/juce_core/native/juce_android_Files.cpp b/source/modules/juce_core/native/juce_android_Files.cpp new file mode 100644 index 000000000..15f90886d --- /dev/null +++ b/source/modules/juce_core/native/juce_android_Files.cpp @@ -0,0 +1,241 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 juce_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 juce_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 juce_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; + + JUCE_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 (JuceAppActivity.launchURL, t.get()); +} + +void File::revealToUser() const +{ +} diff --git a/source/modules/juce_core/native/juce_android_JNIHelpers.h b/source/modules/juce_core/native/juce_android_JNIHelpers.h new file mode 100644 index 000000000..5c3ee0eea --- /dev/null +++ b/source/modules/juce_core/native/juce_android_JNIHelpers.h @@ -0,0 +1,407 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ANDROID_JNIHELPERS_H_INCLUDED +#define JUCE_ANDROID_JNIHELPERS_H_INCLUDED + +#if ! (defined (JUCE_ANDROID_ACTIVITY_CLASSNAME) && defined (JUCE_ANDROID_ACTIVITY_CLASSPATH)) + #error "The JUCE_ANDROID_ACTIVITY_CLASSNAME and JUCE_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 juceString (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 juceString (jstring s) + { + return juceString (getEnv(), s); + } + + LocalRef javaString (const String& s) + { + return LocalRef (getEnv()->NewStringUTF (s.toUTF8())); + } + + LocalRef javaStringFromChar (const juce_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*); + + JUCE_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 JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ + extern "C" __attribute__ ((visibility("default"))) returnType JUCE_JOIN_MACRO (JUCE_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, dpi; +}; + +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(); + + jassert (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" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ + METHOD (deleteView, "deleteView", "(L" JUCE_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" JUCE_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 (JuceAppActivity, JUCE_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 // JUCE_ANDROID_JNIHELPERS_H_INCLUDED diff --git a/source/modules/juce_core/native/juce_android_Misc.cpp b/source/modules/juce_core/native/juce_android_Misc.cpp new file mode 100644 index 000000000..b9d5f733c --- /dev/null +++ b/source/modules/juce_core/native/juce_android_Misc.cpp @@ -0,0 +1,32 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +void Logger::outputDebugString (const String& text) +{ + __android_log_print (ANDROID_LOG_INFO, "JUCE", text.toUTF8()); +} diff --git a/source/modules/juce_core/native/juce_android_Network.cpp b/source/modules/juce_core/native/juce_android_Network.cpp new file mode 100644 index 000000000..92fc2cc8e --- /dev/null +++ b/source/modules/juce_core/native/juce_android_Network.cpp @@ -0,0 +1,175 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +//============================================================================== +#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, JUCE_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 (JuceAppActivity, + JuceAppActivity.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 (juceString (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() override { return stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted); } + int64 getTotalLength() override { return stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0; } + int64 getPosition() override { return stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0; } + bool setPosition (int64 wantedPos) override { return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos); } + + int read (void* buffer, int bytesToRead) override + { + jassert (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: + JUCE_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/source/modules/juce_core/native/juce_android_SystemStats.cpp b/source/modules/juce_core/native/juce_android_SystemStats.cpp new file mode 100644 index 000000000..605faac97 --- /dev/null +++ b/source/modules/juce_core/native/juce_android_SystemStats.cpp @@ -0,0 +1,307 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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)); + jassert (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); + jassert (m != 0); + return m; +} + +jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetStaticMethodID (classRef, methodName, params); + jassert (m != 0); + return m; +} + +jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetFieldID (classRef, fieldName, signature); + jassert (f != 0); + return f; +} + +jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); + jassert (f != 0); + return f; +} + +//============================================================================== +ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; + +#if JUCE_DEBUG +static bool systemInitialised = false; +#endif + +JNIEnv* getEnv() noexcept +{ + #if JUCE_DEBUG + if (! systemInitialised) + { + DBG ("*** Call to getEnv() when system not initialised"); + jassertfalse; + std::exit (EXIT_FAILURE); + } + #endif + + return threadLocalJNIEnvHolder.getOrAttach(); +} + +extern "C" jint JNI_OnLoad (JavaVM*, void*) +{ + return JNI_VERSION_1_2; +} + +//============================================================================== +AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160) +{ +} + +void AndroidSystem::initialise (JNIEnv* env, jobject activity_, + jstring appFile_, jstring appDataDir_) +{ + screenWidth = screenHeight = 0; + dpi = 160; + JNIClassBase::initialiseAllClasses (env); + + threadLocalJNIEnvHolder.initialise (env); + #if JUCE_DEBUG + systemInitialised = true; + #endif + + activity = GlobalRef (activity_); + appFile = juceString (env, appFile_); + appDataDir = juceString (env, appDataDir_); +} + +void AndroidSystem::shutdown (JNIEnv* env) +{ + activity.clear(); + + #if JUCE_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 juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (SystemClass, + SystemClass.getProperty, + javaString (name).get()))); + } + + //============================================================================== + String getLocaleValue (bool isRegion) + { + return juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, + JuceAppActivity.getLocaleValue, + isRegion))); + } +} + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + return Android; +} + +String SystemStats::getOperatingSystemName() +{ + return "Android " + AndroidStatsHelpers::getSystemProperty ("os.version"); +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if JUCE_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(); } + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + numCpus = jmax (1, sysconf (_SC_NPROCESSORS_ONLN)); +} + +//============================================================================== +uint32 juce_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/source/modules/juce_core/native/juce_android_Threads.cpp b/source/modules/juce_core/native/juce_android_Threads.cpp new file mode 100644 index 000000000..7eab46517 --- /dev/null +++ b/source/modules/juce_core/native/juce_android_Threads.cpp @@ -0,0 +1,81 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_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() +{ + std::exit (EXIT_FAILURE); +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + return false; +} + +JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_isRunningUnderDebugger(); +} + +void Process::raisePrivilege() {} +void Process::lowerPrivilege() {} diff --git a/source/modules/juce_core/native/juce_linux_Files.cpp b/source/modules/juce_core/native/juce_linux_Files.cpp new file mode 100644 index 000000000..a953cba2b --- /dev/null +++ b/source/modules/juce_core/native/juce_linux_Files.cpp @@ -0,0 +1,378 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 juce_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 juce_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* juce_argv = nullptr; +int juce_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 (juce_argv != nullptr && juce_argc > 0) + return File (CharPointer_UTF8 (juce_argv[0])); + // deliberate fall-through... + + case currentExecutableFile: + case currentApplicationFile: + return juce_getExecutableFile(); + + case hostApplicationPath: + return juce_readlink ("/proc/self/exe", juce_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; + + JUCE_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) +{ + juce_statStruct info; + + return juce_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/source/modules/juce_core/native/juce_linux_Network.cpp b/source/modules/juce_core/native/juce_linux_Network.cpp new file mode 100644 index 000000000..30badf80e --- /dev/null +++ b/source/modules/juce_core/native/juce_linux_Network.cpp @@ -0,0 +1,462 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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() override { return finished; } + int64 getPosition() override { return position; } + + int64 getTotalLength() override + { + //xxx to do + return -1; + } + + int read (void* buffer, int bytesToRead) override + { + if (finished || isError()) + return 0; + + fd_set readbits; + FD_ZERO (&readbits); + FD_SET (socketHandle, &readbits); + + struct timeval tv; + tv.tv_sec = jmax (1, timeOutMs / 1000); + tv.tv_usec = 0; + + if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) + return 0; // (timeout) + + const int bytesRead = jmax (0, (int) recv (socketHandle, buffer, bytesToRead, MSG_WAITALL)); + if (bytesRead == 0) + finished = true; + position += bytesRead; + return bytesRead; + } + + bool setPosition (int64 wantedPos) override + { + 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 JUCE_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 = jmax (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:", "JUCE/" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) + "." JUCE_STRINGIFY(JUCE_MINOR_VERSION) + "." JUCE_STRINGIFY(JUCE_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 = jmin (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; + } + + JUCE_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/source/modules/juce_core/native/juce_linux_SystemStats.cpp b/source/modules/juce_core/native/juce_linux_SystemStats.cpp new file mode 100644 index 000000000..0c13ff188 --- /dev/null +++ b/source/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -0,0 +1,181 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_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) + if (passwd* const pw = getpwuid (getuid())) + 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; +} + +static String getLocaleValue (nl_item key) +{ + const char* oldLocale = ::setlocale (LC_ALL, ""); + String result (String::fromUTF8 (nl_langinfo (key))); + ::setlocale (LC_ALL, oldLocale); + return result; +} + +String SystemStats::getUserLanguage() { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); } +String SystemStats::getUserRegion() { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); } +String SystemStats::getDisplayLanguage() { return getUserLanguage(); } + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + const String flags (LinuxStatsHelpers::getCpuInfo ("flags")); + hasMMX = flags.contains ("mmx"); + hasSSE = flags.contains ("sse"); + hasSSE2 = flags.contains ("sse2"); + hasSSE3 = flags.contains ("sse3"); + has3DNow = flags.contains ("3dnow"); + + numCpus = LinuxStatsHelpers::getCpuInfo ("processor").getIntValue() + 1; +} + +//============================================================================== +uint32 juce_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/source/modules/juce_core/native/juce_linux_Threads.cpp b/source/modules/juce_core/native/juce_linux_Threads.cpp new file mode 100644 index 000000000..cf406658a --- /dev/null +++ b/source/modules/juce_core/native/juce_linux_Threads.cpp @@ -0,0 +1,94 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_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() +{ + std::exit (EXIT_FAILURE); +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + #if JUCE_BSD + return false; + #else + 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; + #endif +} + +JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_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/source/modules/juce_core/native/juce_mac_Files.mm b/source/modules/juce_core/native/juce_mac_Files.mm new file mode 100644 index 000000000..ccd30df2d --- /dev/null +++ b/source/modules/juce_core/native/juce_mac_Files.mm @@ -0,0 +1,485 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +//============================================================================== +bool File::copyInternal (const File& dest) const +{ + JUCE_AUTORELEASEPOOL + { + NSFileManager* fm = [NSFileManager defaultManager]; + + return [fm fileExistsAtPath: juceStringToNS (fullPath)] + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + && [fm copyItemAtPath: juceStringToNS (fullPath) + toPath: juceStringToNS (dest.getFullPathName()) + error: nil]; + #else + && [fm copyPath: juceStringToNS (fullPath) + toPath: juceStringToNS (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 (juce_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 + JUCE_AUTORELEASEPOOL + { + NSNumber* hidden = nil; + NSError* err = nil; + + return [[NSURL fileURLWithPath: juceStringToNS (path)] + getResourceValue: &hidden forKey: NSURLIsHiddenKey error: &err] + && [hidden boolValue]; + } + #elif JUCE_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 JUCE_IOS + String getIOSSystemLocation (NSSearchPathDirectory type) + { + return nsStringToJuce ([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 JUCE_IOS + return false; // xxx is this possible? + #else + JUCE_AUTORELEASEPOOL + { + BOOL removable = false; + + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: juceStringToNS (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* juce_argv = nullptr; +int juce_argc = 0; + +File File::getSpecialLocation (const SpecialLocationType type) +{ + JUCE_AUTORELEASEPOOL + { + String resultPath; + + switch (type) + { + case userHomeDirectory: resultPath = nsStringToJuce (NSHomeDirectory()); break; + + #if JUCE_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 (juce_getExecutableFile().getFileNameWithoutExtension()); + tmp.createDirectory(); + return tmp.getFullPathName(); + } + + #else + case userDocumentsDirectory: resultPath = "~/Documents"; break; + case userDesktopDirectory: resultPath = "~/Desktop"; break; + + case tempDirectory: + { + File tmp ("~/Library/Caches/" + juce_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 (juce_argv != nullptr && juce_argc > 0) + return File (CharPointer_UTF8 (juce_argv[0])); + // deliberate fall-through... + + case currentExecutableFile: + return juce_getExecutableFile(); + + case currentApplicationFile: + { + const File exe (juce_getExecutableFile()); + const File parent (exe.getParentDirectory()); + + #if JUCE_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 +{ + JUCE_AUTORELEASEPOOL + { + if (NSBundle* bundle = [NSBundle bundleWithPath: juceStringToNS (getFullPathName())]) + if (NSDictionary* info = [bundle infoDictionary]) + if (NSString* name = [info valueForKey: nsStringLiteral ("CFBundleShortVersionString")]) + return nsStringToJuce (name); + } + + return String::empty; +} + +//============================================================================== +File File::getLinkedTarget() const +{ + #if JUCE_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: juceStringToNS (getFullPathName()) error: nil]; + #else + // (the cast here avoids a deprecation warning) + NSString* dest = [((id) [NSFileManager defaultManager]) pathContentOfSymbolicLinkAtPath: juceStringToNS (getFullPathName())]; + #endif + + if (dest != nil) + return File (nsStringToJuce (dest)); + + return *this; +} + +//============================================================================== +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + #if JUCE_IOS + return deleteFile(); //xxx is there a trashcan on the iOS? + #else + JUCE_AUTORELEASEPOOL + { + NSString* p = juceStringToNS (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) + { + JUCE_AUTORELEASEPOOL + { + enumerator = [[[NSFileManager defaultManager] enumeratorAtPath: juceStringToNS (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) + { + JUCE_AUTORELEASEPOOL + { + const char* wildcardUTF8 = nullptr; + + for (;;) + { + NSString* file; + if (enumerator == nil || (file = [enumerator nextObject]) == nil) + return false; + + [enumerator skipDescendents]; + filenameFound = nsStringToJuce (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; + + JUCE_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 JUCE_IOS + return [[UIApplication sharedApplication] openURL: [NSURL URLWithString: juceStringToNS (fileName)]]; + #else + JUCE_AUTORELEASEPOOL + { + if (parameters.isEmpty()) + { + return [[NSWorkspace sharedWorkspace] openFile: juceStringToNS (fileName)] + || [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: juceStringToNS (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: juceStringToNS (docs[i])]; + + ok = [[NSWorkspace sharedWorkspace] openURLs: urls + withAppBundleIdentifier: [[NSBundle bundleWithPath: juceStringToNS (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 ! JUCE_IOS + if (exists()) + [[NSWorkspace sharedWorkspace] selectFile: juceStringToNS (getFullPathName()) inFileViewerRootedAtPath: nsEmptyString()]; + else if (getParentDirectory().exists()) + getParentDirectory().revealToUser(); + #endif +} + +//============================================================================== +OSType File::getMacOSType() const +{ + JUCE_AUTORELEASEPOOL + { + #if JUCE_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: juceStringToNS (getFullPathName()) error: nil]; + #else + // (the cast here avoids a deprecation warning) + NSDictionary* fileDict = [((id) [NSFileManager defaultManager]) fileAttributesAtPath: juceStringToNS (getFullPathName()) traverseLink: NO]; + #endif + + return [fileDict fileHFSTypeCode]; + } +} + +bool File::isBundle() const +{ + #if JUCE_IOS + return false; // xxx can't find a sensible way to do this without trying to open the bundle.. + #else + JUCE_AUTORELEASEPOOL + { + return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (getFullPathName())]; + } + #endif +} + +#if JUCE_MAC +void File::addToDock() const +{ + // check that it's not already there... + if (! juce_getOutputFromCommand ("defaults read com.apple.dock persistent-apps").containsIgnoreCase (getFullPathName())) + { + juce_runSystemCommand ("defaults write com.apple.dock persistent-apps -array-add \"tile-datafile-data_CFURLString" + + getFullPathName() + "_CFURLStringType0\""); + + juce_runSystemCommand ("osascript -e \"tell application \\\"Dock\\\" to quit\""); + } +} +#endif diff --git a/source/modules/juce_core/native/juce_mac_Network.mm b/source/modules/juce_core/native/juce_mac_Network.mm new file mode 100644 index 000000000..88845df36 --- /dev/null +++ b/source/modules/juce_core/native/juce_mac_Network.mm @@ -0,0 +1,432 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_IOS + //xxx probably need to use MFMailComposeViewController + jassertfalse; + return false; + #else + JUCE_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: juceStringToNS (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 = jmin (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 (nsStringToJuce ([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() override + { + connection = [[NSURLConnection alloc] initWithRequest: request + delegate: delegate]; + while (! threadShouldExit()) + { + JUCE_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 ("JUCEAppDelegate_") + { + 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(); + } + }; + + JUCE_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_) + { + JUCE_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 (nsStringToJuce (key), + nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); + } + } + } + + //============================================================================== + bool isError() const { return connection == nullptr; } + int64 getTotalLength() override { return connection == nullptr ? -1 : connection->contentLength; } + bool isExhausted() override { return finished; } + int64 getPosition() override { return position; } + + int read (void* buffer, int bytesToRead) override + { + jassert (buffer != nullptr && bytesToRead >= 0); + + if (finished || isError()) + return 0; + + JUCE_AUTORELEASEPOOL + { + const int bytesRead = connection->read (static_cast (buffer), bytesToRead); + position += bytesRead; + + if (bytesRead == 0) + finished = true; + + return bytesRead; + } + } + + bool setPosition (int64 wantedPos) override + { + 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) + { + jassert (connection == nullptr); + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (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: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (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; + } + } + + JUCE_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/source/modules/juce_core/native/juce_mac_Strings.mm b/source/modules/juce_core/native/juce_mac_Strings.mm new file mode 100644 index 000000000..ac8190052 --- /dev/null +++ b/source/modules/juce_core/native/juce_mac_Strings.mm @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_IOS + JUCE_AUTORELEASEPOOL + { + return nsStringToJuce ([juceStringToNS (*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/source/modules/juce_core/native/juce_mac_SystemStats.mm b/source/modules/juce_core/native/juce_mac_SystemStats.mm new file mode 100644 index 000000000..e4bd18aa1 --- /dev/null +++ b/source/modules/juce_core/native/juce_mac_SystemStats.mm @@ -0,0 +1,292 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_INTEL && ! JUCE_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 JUCE_64BIT + , "b" (lb), "c" (lc), "d" (ld) + #endif + ); + + a = la; b = lb; c = lc; d = ld; + } + #endif +} + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + #if JUCE_INTEL && ! JUCE_NO_INLINE_ASM + uint32 a = 0, b = 0, d = 0, c = 0; + SystemStatsHelpers::doCPUID (a, b, c, d, 1); + + hasMMX = (d & (1u << 23)) != 0; + hasSSE = (d & (1u << 25)) != 0; + hasSSE2 = (d & (1u << 26)) != 0; + has3DNow = (b & (1u << 31)) != 0; + hasSSE3 = (c & (1u << 0)) != 0; + #endif + + #if JUCE_IOS || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + numCpus = (int) [[NSProcessInfo processInfo] activeProcessorCount]; + #else + numCpus = (int) MPProcessors(); + #endif +} + +#if JUCE_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 ! JUCE_IOS +static String getOSXVersion() +{ + JUCE_AUTORELEASEPOOL + { + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile: + nsStringLiteral ("/System/Library/CoreServices/SystemVersion.plist")]; + + return nsStringToJuce ([dict objectForKey: nsStringLiteral ("ProductVersion")]); + } +} +#endif + +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + #if JUCE_IOS + return iOS; + #else + StringArray parts; + parts.addTokens (getOSXVersion(), ".", String::empty); + + jassert (parts[0].getIntValue() == 10); + const int major = parts[1].getIntValue(); + jassert (major > 2); + + return (OperatingSystemType) (major + MacOSX_10_4 - 4); + #endif +} + +String SystemStats::getOperatingSystemName() +{ + #if JUCE_IOS + return "iOS " + nsStringToJuce ([[UIDevice currentDevice] systemVersion]); + #else + return "Mac OSX " + getOSXVersion(); + #endif +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if JUCE_IOS + return false; + #elif JUCE_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 JUCE_INTEL && ! JUCE_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 JUCE_BIG_ENDIAN + if (speedSize == 4) + speedHz >>= 32; + #endif + + return (int) (speedHz / 1000000); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + return nsStringToJuce (NSUserName()); +} + +String SystemStats::getFullUserName() +{ + return nsStringToJuce (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 juce_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/source/modules/juce_core/native/juce_mac_Threads.mm b/source/modules/juce_core/native/juce_mac_Threads.mm new file mode 100644 index 000000000..b739d9645 --- /dev/null +++ b/source/modules/juce_core/native/juce_mac_Threads.mm @@ -0,0 +1,98 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +//============================================================================== +bool Process::isForegroundProcess() +{ + #if JUCE_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 JUCE_MAC + [NSApp activateIgnoringOtherApps: YES]; + #endif +} + +void Process::hide() +{ + #if JUCE_MAC + [NSApp hide: nil]; + #endif +} + +void Process::raisePrivilege() +{ + jassertfalse; +} + +void Process::lowerPrivilege() +{ + jassertfalse; +} + +void Process::terminate() +{ + std::exit (EXIT_FAILURE); +} + +void Process::setPriority (ProcessPriority) +{ + // xxx +} + +//============================================================================== +JUCE_API bool JUCE_CALLTYPE juce_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; +} + +JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_isRunningUnderDebugger(); +} diff --git a/source/modules/juce_core/native/juce_osx_ObjCHelpers.h b/source/modules/juce_core/native/juce_osx_ObjCHelpers.h new file mode 100644 index 000000000..008311421 --- /dev/null +++ b/source/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -0,0 +1,167 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OSX_OBJCHELPERS_H_INCLUDED +#define JUCE_OSX_OBJCHELPERS_H_INCLUDED + + +/* 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 nsStringToJuce (NSString* s) + { + return CharPointer_UTF8 ([s UTF8String]); + } + + static inline NSString* juceStringToNS (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 + static NSRect makeNSRect (const RectangleType& r) noexcept + { + return NSMakeRect (static_cast (r.getX()), + static_cast (r.getY()), + static_cast (r.getWidth()), + static_cast (r.getHeight())); + } +} + +//============================================================================== +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)); + jassert (b); (void) b; + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* signature) + { + BOOL b = class_addMethod (cls, selector, (IMP) callbackFn, signature); + jassert (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); + jassert (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 (juce::Random::getSystemRandom().nextInt64()); + } + + JUCE_DECLARE_NON_COPYABLE (ObjCClass) +}; + + +#endif // JUCE_OSX_OBJCHELPERS_H_INCLUDED diff --git a/source/modules/juce_core/native/juce_posix_NamedPipe.cpp b/source/modules/juce_core/native/juce_posix_NamedPipe.cpp new file mode 100644 index 000000000..c97f8815f --- /dev/null +++ b/source/modules/juce_core/native/juce_posix_NamedPipe.cpp @@ -0,0 +1,219 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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); + juce_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 + : jmin (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); + } + + JUCE_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 JUCE_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/source/modules/juce_core/native/juce_posix_SharedCode.h b/source/modules/juce_core/native/juce_posix_SharedCode.h new file mode 100644 index 000000000..8db1f521a --- /dev/null +++ b/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -0,0 +1,1275 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +CriticalSection::CriticalSection() noexcept +{ + pthread_mutexattr_t atts; + pthread_mutexattr_init (&atts); + pthread_mutexattr_settype (&atts, PTHREAD_MUTEX_RECURSIVE); + #if ! JUCE_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 ! JUCE_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 JUCE_CALLTYPE Thread::sleep (int millisecs) +{ + struct timespec time; + time.tv_sec = millisecs / 1000; + time.tv_nsec = (millisecs % 1000) * 1000000; + nanosleep (&time, nullptr); +} + + +//============================================================================== +const juce_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; +} + +//============================================================================== +// The unix siginterrupt function is deprecated - this does the same job. +int juce_siginterrupt (int sig, int flag) +{ + struct ::sigaction act; + (void) ::sigaction (sig, nullptr, &act); + + if (flag != 0) + act.sa_flags &= ~SA_RESTART; + else + act.sa_flags |= SA_RESTART; + + return ::sigaction (sig, &act, nullptr); +} + +//============================================================================== +namespace +{ + #if JUCE_LINUX || (JUCE_IOS && ! __DARWIN_ONLY_64_BIT_INO_T) // (this iOS stuff is to avoid a simulator bug) + typedef struct stat64 juce_statStruct; + #define JUCE_STAT stat64 + #else + typedef struct stat juce_statStruct; + #define JUCE_STAT stat + #endif + + bool juce_stat (const String& fileName, juce_statStruct& info) + { + return fileName.isNotEmpty() + && JUCE_STAT (fileName.toUTF8(), &info) == 0; + } + + // if this file doesn't exist, find a parent of it that does.. + bool juce_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) + { + juce_statStruct info; + const bool statOk = juce_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 +{ + juce_statStruct info; + + return fullPath.isEmpty() + || (juce_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 +{ + juce_statStruct info; + return juce_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 +{ + juce_statStruct info; + if (! juce_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; + + juce_statStruct info; + if (juce_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 +{ + juce_statStruct info; + + if ((modificationTime != 0 || accessTime != 0) && juce_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 juce_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 JUCE_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 (JuceAppActivity.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) +{ + jassert (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 JUCE_PROJUCER_LIVE_BUILD +extern "C" const char* juce_getCurrentExecutablePath(); +#endif + +File juce_getExecutableFile(); +File juce_getExecutableFile() +{ + #if JUCE_PROJUCER_LIVE_BUILD + return File (juce_getCurrentExecutablePath()); + #elif JUCE_ANDROID + return File (android.appFile); + #else + struct DLAddrReader + { + static String getFilename() + { + Dl_info exeInfo; + dladdr ((void*) juce_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 (juce_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 (juce_doStatFS (*this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_blocks; + + return 0; +} + +String File::getVolumeLabel() const +{ + #if JUCE_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 juce_runSystemCommand (const String&); +void juce_runSystemCommand (const String& command) +{ + int result = system (command.toUTF8()); + (void) result; +} + +String juce_getOutputFromCommand (const String&); +String juce_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)); + + juce_runSystemCommand (command + " > " + tempFile.getFullPathName()); + + String result (tempFile.loadFileAsString()); + tempFile.deleteFile(); + return result; +} + + +//============================================================================== +#if JUCE_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 JUCE_MAC + if (! createLockFile (File ("~/Library/Caches/com.juce.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.juce.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! + jassert (pimpl != nullptr); + + if (pimpl != nullptr && --(pimpl->refCount) == 0) + pimpl = nullptr; +} + +//============================================================================== +void JUCE_API juce_threadEntryPoint (void*); + +extern "C" void* threadEntryProc (void*); +extern "C" void* threadEntryProc (void* userData) +{ + JUCE_AUTORELEASEPOOL + { + #if JUCE_ANDROID + struct AndroidThreadScope + { + AndroidThreadScope() { threadLocalJNIEnvHolder.attach(); } + ~AndroidThreadScope() { threadLocalJNIEnvHolder.detach(); } + }; + + const AndroidThreadScope androidEnv; + #endif + + juce_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 JUCE_ANDROID + jassertfalse; // pthread_cancel not available! + #else + pthread_cancel ((pthread_t) threadHandle); + #endif + } +} + +void Thread::setCurrentThreadName (const String& name) +{ + #if JUCE_IOS || (JUCE_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + JUCE_AUTORELEASEPOOL + { + [[NSThread currentThread] setName: juceStringToNS (name)]; + } + #elif JUCE_LINUX + #if (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 + pthread_setname_np (pthread_self(), name.toRawUTF8()); + #else + prctl (PR_SET_NAME, name.toRawUTF8(), 0, 0, 0); + #endif + #endif +} + +bool Thread::setThreadPriority (void* handle, int priority) +{ + struct sched_param param; + int policy; + priority = jlimit (0, 10, priority); + + if (handle == nullptr) + 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) + { + jassert (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; + + JUCE_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() + { + jassert (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 ! JUCE_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 JUCE_MAC || JUCE_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 JUCE_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 JUCE_MAC || JUCE_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 + } + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/source/modules/juce_core/native/juce_win32_ComSmartPtr.h b/source/modules/juce_core/native/juce_win32_ComSmartPtr.h new file mode 100644 index 000000000..8d7c912f9 --- /dev/null +++ b/source/modules/juce_core/native/juce_win32_ComSmartPtr.h @@ -0,0 +1,170 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WIN32_COMSMARTPTR_H_INCLUDED +#define JUCE_WIN32_COMSMARTPTR_H_INCLUDED + +#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()); + jassert (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 JUCE_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; + + JUCE_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == IID_IUnknown) + return castToType (result); + + *result = 0; + return E_NOINTERFACE; + } + + template + JUCE_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() {} + + JUCE_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == __uuidof (ComClass)) + return this->template castToType (result); + + return ComBaseClassHelperBase ::QueryInterface (refId, result); + } +}; + +#endif // JUCE_WIN32_COMSMARTPTR_H_INCLUDED diff --git a/source/modules/juce_core/native/juce_win32_Files.cpp b/source/modules/juce_core/native/juce_win32_Files.cpp new file mode 100644 index 000000000..61015f603 --- /dev/null +++ b/source/modules/juce_core/native/juce_win32_Files.cpp @@ -0,0 +1,961 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#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_jassert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME)); // tell me if this fails! + + return (int64) ((reinterpret_cast (ft)->QuadPart - 116444736000000000LL) / 10000); + } + + FILETIME* timeToFileTime (const int64 time, FILETIME* const ft) noexcept + { + if (time <= 0) + return nullptr; + + reinterpret_cast (ft)->QuadPart = (ULONGLONG) (time * 10000 + 116444736000000000LL); + 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 juce_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 juce_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) +{ + jassert (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 JUCE_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; + + JUCE_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; + + JUCE_TRY + { + hInstance = ShellExecute (0, 0, fileName.toWideCharPointer(), + parameters.toWideCharPointer(), 0, SW_SHOWDEFAULT); + } + JUCE_CATCH_ALL + + return hInstance > (HINSTANCE) 32; +} + +void File::revealToUser() const +{ + DynamicLibrary dll ("Shell32.dll"); + JUCE_LOAD_WINAPI_FUNCTION (dll, ILCreateFromPathW, ilCreateFromPathW, ITEMIDLIST*, (LPCWSTR)) + JUCE_LOAD_WINAPI_FUNCTION (dll, ILFree, ilFree, void, (ITEMIDLIST*)) + JUCE_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; + } + + JUCE_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/source/modules/juce_core/native/juce_win32_Network.cpp b/source/modules/juce_core/native/juce_win32_Network.cpp new file mode 100644 index 000000000..11b305750 --- /dev/null +++ b/source/modules/juce_core/native/juce_win32_Network.cpp @@ -0,0 +1,469 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#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) + { + jassert (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("juce"), 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 = jmin (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(); + } + + JUCE_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"); + JUCE_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"); + JUCE_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"); + JUCE_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/source/modules/juce_core/native/juce_win32_Registry.cpp b/source/modules/juce_core/native/juce_win32_Registry.cpp new file mode 100644 index 000000000..c740745aa --- /dev/null +++ b/source/modules/juce_core/native/juce_win32_Registry.cpp @@ -0,0 +1,236 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 keyExists (const String& regValuePath, const DWORD wow64Flags) + { + return RegistryKeyWrapper (regValuePath, false, wow64Flags).key != 0; + } + + 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; + + JUCE_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::keyExistsWow64 (const String& regValuePath) +{ + return RegistryKeyWrapper::keyExists (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); +} + +bool WindowsRegistry::keyExists (const String& regValuePath) +{ + return RegistryKeyWrapper::keyExists (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/source/modules/juce_core/native/juce_win32_SystemStats.cpp b/source/modules/juce_core/native/juce_win32_SystemStats.cpp new file mode 100644 index 000000000..7855b397e --- /dev/null +++ b/source/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -0,0 +1,435 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +void Logger::outputDebugString (const String& text) +{ + OutputDebugString ((text + "\n").toWideCharPointer()); +} + +//============================================================================== +#ifdef JUCE_DLL_BUILD + JUCE_API void* juceDLL_malloc (size_t sz) { return std::malloc (sz); } + JUCE_API void juceDLL_free (void* block) { std::free (block); } +#endif + +//============================================================================== +#if JUCE_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 juce_getCpuVendor (char* const v) +{ + int vendor[4] = { 0 }; + + #if ! JUCE_MINGW + __try + #endif + { + #if JUCE_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 ! JUCE_MINGW + __except (EXCEPTION_EXECUTE_HANDLER) + { + } + #endif + + memcpy (v, vendor, 16); +} + +String SystemStats::getCpuVendor() +{ + char v [16]; + juce_getCpuVendor (v); + return String (v, 16); +} +#endif + + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + hasMMX = IsProcessorFeaturePresent (PF_MMX_INSTRUCTIONS_AVAILABLE) != 0; + hasSSE = IsProcessorFeaturePresent (PF_XMMI_INSTRUCTIONS_AVAILABLE) != 0; + hasSSE2 = IsProcessorFeaturePresent (PF_XMMI64_INSTRUCTIONS_AVAILABLE) != 0; + hasSSE3 = IsProcessorFeaturePresent (13 /*PF_SSE3_INSTRUCTIONS_AVAILABLE*/) != 0; + has3DNow = IsProcessorFeaturePresent (7 /*PF_AMD3D_INSTRUCTIONS_AVAILABLE*/) != 0; + + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + numCpus = (int) systemInfo.dwNumberOfProcessors; +} + +#if JUCE_MSVC && JUCE_CHECK_MEMORY_LEAKS +struct DebugFlagsInitialiser +{ + DebugFlagsInitialiser() + { + _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + } +}; + +static DebugFlagsInitialiser debugFlagsInitialiser; +#endif + +//============================================================================== +static bool isWindowsVersionOrLater (SystemStats::OperatingSystemType target) +{ + OSVERSIONINFOEX info; + zerostruct (info); + info.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEX); + + if (target >= SystemStats::WinVista) + { + info.dwMajorVersion = 6; + + switch (target) + { + case SystemStats::WinVista: info.dwMinorVersion = 0; break; + case SystemStats::Windows7: info.dwMinorVersion = 1; break; + case SystemStats::Windows8: info.dwMinorVersion = 2; break; + default: jassertfalse; break; + } + } + else + { + info.dwMajorVersion = 5; + info.dwMinorVersion = target >= SystemStats::WinXP ? 1 : 0; + } + + DWORDLONG mask = 0; + + VER_SET_CONDITION (mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION (mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION (mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION (mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + return VerifyVersionInfo (&info, + VER_MAJORVERSION | VER_MINORVERSION + | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + mask) != FALSE; +} + +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + const SystemStats::OperatingSystemType types[] + = { Windows8, Windows7, WinVista, WinXP, Win2000 }; + + for (int i = 0; i < numElementsInArray (types); ++i) + if (isWindowsVersionOrLater (types[i])) + return types[i]; + + 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 JUCE_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 juce_millisecondsSinceStartup() noexcept +{ + return (uint32) timeGetTime(); +} + +//============================================================================== +class HiResCounterHandler +{ +public: + HiResCounterHandler() + : hiResTicksOffset (0) + { + const MMRESULT res = timeBeginPeriod (1); + (void) res; + jassert (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 = (juce_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 juce_getClockCycleCounter() noexcept +{ + #if JUCE_USE_INTRINSICS + // MS intrinsics version... + return (int64) __rdtsc(); + + #elif JUCE_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 = juce_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 = juce_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"); + JUCE_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/source/modules/juce_core/native/juce_win32_Threads.cpp b/source/modules/juce_core/native/juce_win32_Threads.cpp new file mode 100644 index 000000000..7ea9a348f --- /dev/null +++ b/source/modules/juce_core/native/juce_win32_Threads.cpp @@ -0,0 +1,639 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +HWND juce_messageWindowHandle = 0; // (this is used by other parts of the codebase) + +//============================================================================== +#if ! JUCE_USE_INTRINSICS +// In newer compilers, the inline versions of these are used (in juce_Atomic.h), but in +// older ones we have to actually call the ops as win32 functions.. +long juce_InterlockedExchange (volatile long* a, long b) noexcept { return InterlockedExchange (a, b); } +long juce_InterlockedIncrement (volatile long* a) noexcept { return InterlockedIncrement (a); } +long juce_InterlockedDecrement (volatile long* a) noexcept { return InterlockedDecrement (a); } +long juce_InterlockedExchangeAdd (volatile long* a, long b) noexcept { return InterlockedExchangeAdd (a, b); } +long juce_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept { return InterlockedCompareExchange (a, b, c); } + +__int64 juce_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 JUCE_VC7_OR_EARLIER + static_jassert (sizeof (CRITICAL_SECTION) <= 24); + #else + static_jassert (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 JUCE_API juce_threadEntryPoint (void*); + +static unsigned int __stdcall threadEntryProc (void* userData) +{ + if (juce_messageWindowHandle != 0) + AttachThreadInput (GetWindowThreadProcessId (juce_messageWindowHandle, 0), + GetCurrentThreadId(), TRUE); + + juce_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 JUCE_DEBUG + OutputDebugStringA ("** Warning - Forced thread termination **\n"); + #endif + TerminateThread (threadHandle, 0); + } +} + +void Thread::setCurrentThreadName (const String& name) +{ + #if JUCE_DEBUG && JUCE_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 JUCE_DEBUG + _T("JUCE Sleep Event"))) + #else + nullptr)) + #endif + {} + + ~SleepEvent() noexcept + { + CloseHandle (handle); + handle = 0; + } + + HANDLE handle; +}; + +static SleepEvent sleepEvent; + +void JUCE_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 juce_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; + juce_repeatLastProcessPriority(); + } +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + return IsDebuggerPresent() != FALSE; +} + +bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_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 JUCE_MSVC && JUCE_CHECK_MEMORY_LEAKS + _CrtDumpMemoryLeaks(); + #endif + + // bullet in the head in case there's a problem shutting down.. + ExitProcess (1); +} + +bool juce_isRunningInWine() +{ + HMODULE ntdll = GetModuleHandleA ("ntdll"); + return ntdll != 0 && GetProcAddress (ntdll, "wine_get_version") != nullptr; +} + +//============================================================================== +bool DynamicLibrary::open (const String& name) +{ + close(); + + JUCE_TRY + { + handle = LoadLibrary (name.toWideCharPointer()); + } + JUCE_CATCH_ALL + + return handle != nullptr; +} + +void DynamicLibrary::close() +{ + JUCE_TRY + { + if (handle != nullptr) + { + FreeLibrary ((HMODULE) handle); + handle = nullptr; + } + } + JUCE_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! + jassert (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 = jmin ((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; + + JUCE_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() + { + jassert (periodMs == 0); + } + + void start (int newPeriod) + { + if (newPeriod != periodMs) + { + stop(); + periodMs = newPeriod; + + TIMECAPS tc; + if (timeGetDevCaps (&tc, sizeof (tc)) == TIMERR_NOERROR) + { + const int actualPeriod = jlimit ((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(); + } + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/source/modules/juce_core/network/juce_IPAddress.cpp b/source/modules/juce_core/network/juce_IPAddress.cpp new file mode 100644 index 000000000..0a7bd41f7 --- /dev/null +++ b/source/modules/juce_core/network/juce_IPAddress.cpp @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 ! JUCE_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 JUCE_MAC || JUCE_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/source/modules/juce_core/network/juce_IPAddress.h b/source/modules/juce_core/network/juce_IPAddress.h new file mode 100644 index 000000000..1f2f0e810 --- /dev/null +++ b/source/modules/juce_core/network/juce_IPAddress.h @@ -0,0 +1,82 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_IPADDRESS_H_INCLUDED +#define JUCE_IPADDRESS_H_INCLUDED + + +//============================================================================== +/** + An IPV4 address. +*/ +class JUCE_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 // JUCE_IPADDRESS_H_INCLUDED diff --git a/source/modules/juce_core/network/juce_MACAddress.cpp b/source/modules/juce_core/network/juce_MACAddress.cpp new file mode 100644 index 000000000..b7cb3c115 --- /dev/null +++ b/source/modules/juce_core/network/juce_MACAddress.cpp @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/network/juce_MACAddress.h b/source/modules/juce_core/network/juce_MACAddress.h new file mode 100644 index 000000000..c07e1d2cf --- /dev/null +++ b/source/modules/juce_core/network/juce_MACAddress.h @@ -0,0 +1,89 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MACADDRESS_H_INCLUDED +#define JUCE_MACADDRESS_H_INCLUDED + +#include "../containers/juce_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 JUCE_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 // JUCE_MACADDRESS_H_INCLUDED diff --git a/source/modules/juce_core/network/juce_NamedPipe.cpp b/source/modules/juce_core/network/juce_NamedPipe.cpp new file mode 100644 index 000000000..488e63f41 --- /dev/null +++ b/source/modules/juce_core/network/juce_NamedPipe.cpp @@ -0,0 +1,66 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/network/juce_NamedPipe.h b/source/modules/juce_core/network/juce_NamedPipe.h new file mode 100644 index 000000000..961b2157f --- /dev/null +++ b/source/modules/juce_core/network/juce_NamedPipe.h @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NAMEDPIPE_H_INCLUDED +#define JUCE_NAMEDPIPE_H_INCLUDED + +#include "../threads/juce_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 JUCE_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: + //============================================================================== + JUCE_PUBLIC_IN_DLL_BUILD (class Pimpl) + ScopedPointer pimpl; + String currentPipeName; + ReadWriteLock lock; + + bool openInternal (const String& pipeName, const bool createPipe); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NamedPipe) +}; + + +#endif // JUCE_NAMEDPIPE_H_INCLUDED diff --git a/source/modules/juce_core/network/juce_Socket.cpp b/source/modules/juce_core/network/juce_Socket.cpp new file mode 100644 index 000000000..c902d414d --- /dev/null +++ b/source/modules/juce_core/network/juce_Socket.cpp @@ -0,0 +1,593 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#if JUCE_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 JUCE_WINDOWS + typedef int juce_socklen_t; + typedef SOCKET SocketHandle; +#else + typedef socklen_t juce_socklen_t; + typedef int SocketHandle; +#endif + +//============================================================================== +namespace SocketHelpers +{ + static void initSockets() + { + #if JUCE_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 JUCE_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 JUCE_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; + juce_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 JUCE_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 JUCE_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& host, int portNum, int h) + : hostName (host), + portNumber (portNum), + handle (h), + connected (true), + isListener (false) +{ + SocketHelpers::initSockets(); + SocketHelpers::resetSocketOptions (h, 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 JUCE_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 JUCE_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 +{ + // To call this method, you first have to use createListener() to + // prepare this socket as a listener. + jassert (isListener || ! connected); + + if (connected && isListener) + { + struct sockaddr_storage address; + juce_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 canBroadcast) + : portNumber (0), + handle (-1), + connected (true), + allowBroadcast (canBroadcast), + serverAddress (nullptr) +{ + SocketHelpers::initSockets(); + + handle = (int) socket (AF_INET, SOCK_DGRAM, 0); + bindToPort (localPortNumber); +} + +DatagramSocket::DatagramSocket (const String& host, const int portNum, + const int h, const int localPortNumber) + : hostName (host), + portNumber (portNum), + handle (h), + connected (true), + allowBroadcast (false), + serverAddress (nullptr) +{ + SocketHelpers::initSockets(); + + SocketHelpers::resetSocketOptions (h, true, allowBroadcast); + bindToPort (localPortNumber); +} + +DatagramSocket::~DatagramSocket() +{ + close(); + + if (serverAddress != nullptr) + freeaddrinfo (static_cast (serverAddress)); +} + +void DatagramSocket::close() +{ + #if JUCE_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; + juce_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.. + jassert (serverAddress != nullptr && connected); + + return connected ? (int) sendto (handle, (const char*) sourceBuffer, + (size_t) numBytesToWrite, 0, + static_cast (serverAddress)->ai_addr, + (juce_socklen_t) static_cast (serverAddress)->ai_addrlen) + : -1; +} + +bool DatagramSocket::isLocal() const noexcept +{ + return hostName == "127.0.0.1"; +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif diff --git a/source/modules/juce_core/network/juce_Socket.h b/source/modules/juce_core/network/juce_Socket.h new file mode 100644 index 000000000..0780a1b83 --- /dev/null +++ b/source/modules/juce_core/network/juce_Socket.h @@ -0,0 +1,307 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SOCKET_H_INCLUDED +#define JUCE_SOCKET_H_INCLUDED + +#include "../text/juce_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 JUCE_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); + + JUCE_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 JUCE_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); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DatagramSocket) +}; + + +#endif // JUCE_SOCKET_H_INCLUDED diff --git a/source/modules/juce_core/network/juce_URL.cpp b/source/modules/juce_core/network/juce_URL.cpp new file mode 100644 index 000000000..fc46865c5 --- /dev/null +++ b/source/modules/juce_core/network/juce_URL.cpp @@ -0,0 +1,473 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) + { + jassert (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) ? jmax (end1, end2) + : jmin (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 +{ + jassert (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 ((juce_wchar) (uint8) utf8 [i + 1]); + const int hexDigit2 = CharacterFunctions::getHexDigitValue ((juce_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 ((juce_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/source/modules/juce_core/network/juce_URL.h b/source/modules/juce_core/network/juce_URL.h new file mode 100644 index 000000000..5578a439e --- /dev/null +++ b/source/modules/juce_core/network/juce_URL.h @@ -0,0 +1,350 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_URL_H_INCLUDED +#define JUCE_URL_H_INCLUDED + +#include "../text/juce_StringPairArray.h" +#include "../files/juce_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 JUCE_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); + JUCE_LEAK_DETECTOR (URL) +}; + + +#endif // JUCE_URL_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_BufferedInputStream.cpp b/source/modules/juce_core/streams/juce_BufferedInputStream.cpp new file mode 100644 index 000000000..ec01f8e1b --- /dev/null +++ b/source/modules/juce_core/streams/juce_BufferedInputStream.cpp @@ -0,0 +1,198 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +namespace +{ + int calcBufferStreamBufferSize (int requestedSize, InputStream* const source) noexcept + { + // You need to supply a real stream when creating a BufferedInputStream + jassert (source != nullptr); + + requestedSize = jmax (256, requestedSize); + + const int64 sourceSize = source->getTotalLength(); + if (sourceSize >= 0 && sourceSize < requestedSize) + requestedSize = jmax (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 = jmax ((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) +{ + jassert (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 = jmin (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/source/modules/juce_core/streams/juce_BufferedInputStream.h b/source/modules/juce_core/streams/juce_BufferedInputStream.h new file mode 100644 index 000000000..47035a85f --- /dev/null +++ b/source/modules/juce_core/streams/juce_BufferedInputStream.h @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BUFFEREDINPUTSTREAM_H_INCLUDED +#define JUCE_BUFFEREDINPUTSTREAM_H_INCLUDED + +#include "juce_InputStream.h" +#include "../memory/juce_OptionalScopedPointer.h" +#include "../memory/juce_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 JUCE_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() override; + int64 getPosition() override; + bool setPosition (int64 newPosition) override; + int read (void* destBuffer, int maxBytesToRead) override; + String readString() override; + bool isExhausted() override; + + +private: + //============================================================================== + OptionalScopedPointer source; + int bufferSize; + int64 position, lastReadPos, bufferStart, bufferOverlap; + HeapBlock buffer; + void ensureBuffered(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferedInputStream) +}; + +#endif // JUCE_BUFFEREDINPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_FileInputSource.cpp b/source/modules/juce_core/streams/juce_FileInputSource.cpp new file mode 100644 index 000000000..51b9db906 --- /dev/null +++ b/source/modules/juce_core/streams/juce_FileInputSource.cpp @@ -0,0 +1,56 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/streams/juce_FileInputSource.h b/source/modules/juce_core/streams/juce_FileInputSource.h new file mode 100644 index 000000000..7ce8ae9cb --- /dev/null +++ b/source/modules/juce_core/streams/juce_FileInputSource.h @@ -0,0 +1,69 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEINPUTSOURCE_H_INCLUDED +#define JUCE_FILEINPUTSOURCE_H_INCLUDED + +#include "juce_InputSource.h" +#include "../files/juce_File.h" + + +//============================================================================== +/** + A type of InputSource that represents a normal file. + + @see InputSource +*/ +class JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputSource) +}; + + +#endif // JUCE_FILEINPUTSOURCE_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_InputSource.h b/source/modules/juce_core/streams/juce_InputSource.h new file mode 100644 index 000000000..8e214c487 --- /dev/null +++ b/source/modules/juce_core/streams/juce_InputSource.h @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_INPUTSOURCE_H_INCLUDED +#define JUCE_INPUTSOURCE_H_INCLUDED + +#include "juce_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 JUCE_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: + //============================================================================== + JUCE_LEAK_DETECTOR (InputSource) +}; + + +#endif // JUCE_INPUTSOURCE_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_InputStream.cpp b/source/modules/juce_core/streams/juce_InputStream.cpp new file mode 100644 index 000000000..08ff61ccd --- /dev/null +++ b/source/modules/juce_core/streams/juce_InputStream.cpp @@ -0,0 +1,236 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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_jassert (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) jmin (numBytesToSkip, (int64) 16384); + HeapBlock temp ((size_t) skipBufferSize); + + while (numBytesToSkip > 0 && ! isExhausted()) + numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize)); + } +} diff --git a/source/modules/juce_core/streams/juce_InputStream.h b/source/modules/juce_core/streams/juce_InputStream.h new file mode 100644 index 000000000..d29df065a --- /dev/null +++ b/source/modules/juce_core/streams/juce_InputStream.h @@ -0,0 +1,269 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_INPUTSTREAM_H_INCLUDED +#define JUCE_INPUTSTREAM_H_INCLUDED + +#include "../text/juce_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 JUCE_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 - non-zero 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: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputStream) +}; + +#endif // JUCE_INPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_MemoryInputStream.cpp b/source/modules/juce_core/streams/juce_MemoryInputStream.cpp new file mode 100644 index 000000000..de64475c0 --- /dev/null +++ b/source/modules/juce_core/streams/juce_MemoryInputStream.cpp @@ -0,0 +1,160 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) +{ + jassert (buffer != nullptr && howMany >= 0); + + const int num = jmin (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) jlimit ((int64) 0, (int64) dataSize, pos); + return true; +} + +int64 MemoryInputStream::getPosition() +{ + return position; +} + + +//============================================================================== +#if JUCE_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() + { + juce_wchar buffer [50] = { 0 }; + Random r; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } +}; + +static MemoryStreamTests memoryInputStreamUnitTests; + +#endif diff --git a/source/modules/juce_core/streams/juce_MemoryInputStream.h b/source/modules/juce_core/streams/juce_MemoryInputStream.h new file mode 100644 index 000000000..509c3eb29 --- /dev/null +++ b/source/modules/juce_core/streams/juce_MemoryInputStream.h @@ -0,0 +1,100 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYINPUTSTREAM_H_INCLUDED +#define JUCE_MEMORYINPUTSTREAM_H_INCLUDED + +#include "juce_InputStream.h" +#include "../memory/juce_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 JUCE_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() override; + bool setPosition (int64 pos) override; + int64 getTotalLength() override; + bool isExhausted() override; + int read (void* destBuffer, int maxBytesToRead) override; + +private: + //============================================================================== + const void* data; + size_t dataSize, position; + HeapBlock internalCopy; + + void createInternalCopy(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryInputStream) +}; + +#endif // JUCE_MEMORYINPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_MemoryOutputStream.cpp b/source/modules/juce_core/streams/juce_MemoryOutputStream.cpp new file mode 100644 index 000000000..7d07ce627 --- /dev/null +++ b/source/modules/juce_core/streams/juce_MemoryOutputStream.cpp @@ -0,0 +1,214 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +MemoryOutputStream::MemoryOutputStream (const size_t initialSize) + : blockToUse (&internalBlock), externalData (nullptr), + position (0), size (0), availableSize (0) +{ + internalBlock.setSize (initialSize, false); +} + +MemoryOutputStream::MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + const bool appendToExistingBlockContent) + : blockToUse (&memoryBlockToWriteTo), externalData (nullptr), + position (0), size (0), availableSize (0) +{ + if (appendToExistingBlockContent) + position = size = memoryBlockToWriteTo.getSize(); +} + +MemoryOutputStream::MemoryOutputStream (void* destBuffer, size_t destBufferSize) + : blockToUse (nullptr), externalData (destBuffer), + position (0), size (0), availableSize (destBufferSize) +{ + jassert (externalData != nullptr); // This must be a valid pointer. +} + +MemoryOutputStream::~MemoryOutputStream() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::flush() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::trimExternalBlockSize() +{ + if (blockToUse != &internalBlock && blockToUse != nullptr) + blockToUse->setSize (size, false); +} + +void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) +{ + if (blockToUse != nullptr) + blockToUse->ensureSize (bytesToPreallocate + 1); +} + +void MemoryOutputStream::reset() noexcept +{ + position = 0; + size = 0; +} + +char* MemoryOutputStream::prepareToWrite (size_t numBytes) +{ + jassert ((ssize_t) numBytes >= 0); + size_t storageNeeded = position + numBytes; + + char* data; + + if (blockToUse != nullptr) + { + if (storageNeeded >= blockToUse->getSize()) + blockToUse->ensureSize ((storageNeeded + jmin (storageNeeded / 2, (size_t) (1024 * 1024)) + 32) & ~31u); + + data = static_cast (blockToUse->getData()); + } + else + { + if (storageNeeded > availableSize) + return nullptr; + + data = static_cast (externalData); + } + + char* const writePointer = data + position; + position += numBytes; + size = jmax (size, position); + return writePointer; +} + +bool MemoryOutputStream::write (const void* const buffer, size_t howMany) +{ + jassert (buffer != nullptr); + + if (howMany == 0) + return true; + + if (char* dest = prepareToWrite (howMany)) + { + memcpy (dest, buffer, howMany); + return true; + } + + return false; +} + +bool MemoryOutputStream::writeRepeatedByte (uint8 byte, size_t howMany) +{ + if (howMany == 0) + return true; + + if (char* dest = prepareToWrite (howMany)) + { + memset (dest, byte, howMany); + return true; + } + + return false; +} + +bool MemoryOutputStream::appendUTF8Char (juce_wchar c) +{ + if (char* dest = prepareToWrite (CharPointer_UTF8::getBytesRequiredFor (c))) + { + CharPointer_UTF8 (dest).write (c); + return true; + } + + return false; +} + +MemoryBlock MemoryOutputStream::getMemoryBlock() const +{ + return MemoryBlock (getData(), getDataSize()); +} + +const void* MemoryOutputStream::getData() const noexcept +{ + if (blockToUse == nullptr) + return externalData; + + if (blockToUse->getSize() > size) + static_cast (blockToUse->getData()) [size] = 0; + + return blockToUse->getData(); +} + +bool MemoryOutputStream::setPosition (int64 newPosition) +{ + if (newPosition <= (int64) size) + { + // ok to seek backwards + position = jlimit ((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; + + if (blockToUse != nullptr) + preallocate (blockToUse->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& JUCE_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/source/modules/juce_core/streams/juce_MemoryOutputStream.h b/source/modules/juce_core/streams/juce_MemoryOutputStream.h new file mode 100644 index 000000000..06c08d606 --- /dev/null +++ b/source/modules/juce_core/streams/juce_MemoryOutputStream.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED +#define JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED + +#include "juce_OutputStream.h" +#include "../memory/juce_MemoryBlock.h" +#include "../memory/juce_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 JUCE_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); + + /** Creates a MemoryOutputStream that will write into a user-supplied, fixed-size + block of memory. + When using this mode, the stream will write directly into this memory area until + it's full, at which point write operations will fail. + */ + MemoryOutputStream (void* destBuffer, size_t destBufferSize); + + /** 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 */ + bool appendUTF8Char (juce_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*, size_t) override; + int64 getPosition() override { return position; } + bool setPosition (int64) override; + int writeFromInputStream (InputStream&, int64 maxNumBytesToWrite) override; + bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) override; + +private: + //============================================================================== + MemoryBlock* const blockToUse; + MemoryBlock internalBlock; + void* externalData; + size_t position, size, availableSize; + + void trimExternalBlockSize(); + char* prepareToWrite (size_t); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryOutputStream) +}; + +/** Copies all the data that has been written to a MemoryOutputStream into another stream. */ +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead); + + +#endif // JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_OutputStream.cpp b/source/modules/juce_core/streams/juce_OutputStream.cpp new file mode 100644 index 000000000..5a1093874 --- /dev/null +++ b/source/modules/juce_core/streams/juce_OutputStream.cpp @@ -0,0 +1,336 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#if JUCE_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.. + */ + jassert (activeStreams.size() == 0); + } + + Array activeStreams; +}; + +static DanglingStreamChecker danglingStreamChecker; +#endif + +//============================================================================== +OutputStream::OutputStream() + : newLineString (NewLine::getDefault()) +{ + #if JUCE_DEBUG + danglingStreamChecker.activeStreams.add (this); + #endif +} + +OutputStream::~OutputStream() +{ + #if JUCE_DEBUG + danglingStreamChecker.activeStreams.removeFirstMatchingValue (this); + #endif +} + +//============================================================================== +bool OutputStream::writeBool (const bool b) +{ + return writeByte (b ? (char) 1 + : (char) 0); +} + +bool OutputStream::writeByte (char byte) +{ + return write (&byte, 1); +} + +bool OutputStream::writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) +{ + for (size_t i = 0; i < numTimesToRepeat; ++i) + if (! writeByte ((char) byte)) + return false; + + return true; +} + +bool OutputStream::writeShort (short value) +{ + const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); + return write (&v, 2); +} + +bool OutputStream::writeShortBigEndian (short value) +{ + const unsigned short v = ByteOrder::swapIfLittleEndian ((unsigned short) value); + return write (&v, 2); +} + +bool OutputStream::writeInt (int value) +{ + const unsigned int v = ByteOrder::swapIfBigEndian ((unsigned int) value); + return write (&v, 4); +} + +bool OutputStream::writeIntBigEndian (int value) +{ + const unsigned int v = ByteOrder::swapIfLittleEndian ((unsigned int) value); + return write (&v, 4); +} + +bool 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; + + return write (data, num + 1); +} + +bool OutputStream::writeInt64 (int64 value) +{ + const uint64 v = ByteOrder::swapIfBigEndian ((uint64) value); + return write (&v, 8); +} + +bool OutputStream::writeInt64BigEndian (int64 value) +{ + const uint64 v = ByteOrder::swapIfLittleEndian ((uint64) value); + return write (&v, 8); +} + +bool OutputStream::writeFloat (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + return writeInt (n.asInt); +} + +bool OutputStream::writeFloatBigEndian (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + return writeIntBigEndian (n.asInt); +} + +bool OutputStream::writeDouble (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + return writeInt64 (n.asInt); +} + +bool OutputStream::writeDoubleBigEndian (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + return writeInt64BigEndian (n.asInt); +} + +bool 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); + return write (temp, numBytes); +} + +bool 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 juce_wchar c = src.getAndAdvance(); + + if (c == 0) + break; + + if (c == '\n' && ! lastCharWasReturn) + writeShort ((short) '\r'); + + lastCharWasReturn = (c == L'\r'); + + if (! writeShort ((short) c)) + return false; + } + } + else + { + const char* src = text.toUTF8(); + const char* t = src; + + for (;;) + { + if (*t == '\n') + { + if (t > src) + if (! write (src, (int) (t - src))) + return false; + + if (! write ("\r\n", 2)) + return false; + + src = t + 1; + } + else if (*t == '\r') + { + if (t[1] == '\n') + ++t; + } + else if (*t == 0) + { + if (t > src) + if (! write (src, (int) (t - src))) + return false; + + break; + } + + ++t; + } + } + + return true; +} + +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) jmin (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_; +} + +//============================================================================== +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int number) +{ + return stream << String (number); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int64 number) +{ + return stream << String (number); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const double number) +{ + return stream << String (number); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char character) +{ + stream.writeByte (character); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* const text) +{ + stream.write (text, strlen (text)); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data) +{ + if (data.getSize() > 0) + stream.write (data.getData(), data.getSize()); + + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) +{ + FileInputStream in (fileToRead); + + if (in.openedOk()) + return stream << in; + + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead) +{ + stream.writeFromInputStream (streamToRead, -1); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&) +{ + return stream << stream.getNewLineString(); +} diff --git a/source/modules/juce_core/streams/juce_OutputStream.h b/source/modules/juce_core/streams/juce_OutputStream.h new file mode 100644 index 000000000..beb7d3b90 --- /dev/null +++ b/source/modules/juce_core/streams/juce_OutputStream.h @@ -0,0 +1,284 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OUTPUTSTREAM_H_INCLUDED +#define JUCE_OUTPUTSTREAM_H_INCLUDED + +#include "../text/juce_String.h" +#include "../text/juce_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 JUCE_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. + @returns false if the write operation fails for some reason + @see InputStream::readByte + */ + virtual bool 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. + @returns false if the write operation fails for some reason + @see InputStream::readBool + */ + virtual bool 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). + @returns false if the write operation fails for some reason + @see InputStream::readShort + */ + virtual bool 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). + @returns false if the write operation fails for some reason + @see InputStream::readShortBigEndian + */ + virtual bool writeShortBigEndian (short value); + + /** Writes a 32-bit integer to the stream in a little-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt + */ + virtual bool writeInt (int value); + + /** Writes a 32-bit integer to the stream in a big-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readIntBigEndian + */ + virtual bool writeIntBigEndian (int value); + + /** Writes a 64-bit integer to the stream in a little-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt64 + */ + virtual bool writeInt64 (int64 value); + + /** Writes a 64-bit integer to the stream in a big-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt64BigEndian + */ + virtual bool 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. + @returns false if the write operation fails for some reason + @see InputStream::readFloat + */ + virtual bool 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. + @returns false if the write operation fails for some reason + @see InputStream::readFloatBigEndian + */ + virtual bool 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. + @returns false if the write operation fails for some reason + @see InputStream::readDouble + */ + virtual bool 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 + @returns false if the write operation fails for some reason + */ + virtual bool writeDoubleBigEndian (double value); + + /** Writes a byte to the output stream a given number of times. + @returns false if the write operation fails for some reason + */ + virtual bool 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. + + @returns false if the write operation fails for some reason + @see InputStream::readCompressedInt + */ + virtual bool 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<< + + @returns false if the write operation fails for some reason + @see InputStream::readString, writeText, operator<< + */ + virtual bool 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'. + @returns false if the write operation fails for some reason + */ + virtual bool 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) + @returns the number of bytes written + */ + 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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OutputStream) +}; + +//============================================================================== +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, int number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, int64 number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, double number); + +/** Writes a character to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, char character); + +/** Writes a null-terminated text string to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* text); + +/** Writes a block of data from a MemoryBlock to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data); + +/** Writes the contents of a file to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead); + +/** Writes the complete contents of an input stream to an output stream. */ +JUCE_API OutputStream& JUCE_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 +*/ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&); + + +#endif // JUCE_OUTPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/streams/juce_SubregionStream.cpp b/source/modules/juce_core/streams/juce_SubregionStream.cpp new file mode 100644 index 000000000..c998c7984 --- /dev/null +++ b/source/modules/juce_core/streams/juce_SubregionStream.cpp @@ -0,0 +1,82 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 ? jmin (lengthOfSourceStream, srcLen) + : srcLen; +} + +int64 SubregionStream::getPosition() +{ + return source->getPosition() - startPositionInSourceStream; +} + +bool SubregionStream::setPosition (int64 newPosition) +{ + return source->setPosition (jmax ((int64) 0, newPosition + startPositionInSourceStream)); +} + +int SubregionStream::read (void* destBuffer, int maxBytesToRead) +{ + jassert (destBuffer != nullptr && maxBytesToRead >= 0); + + if (lengthOfSourceStream < 0) + return source->read (destBuffer, maxBytesToRead); + + maxBytesToRead = (int) jmin ((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/source/modules/juce_core/streams/juce_SubregionStream.h b/source/modules/juce_core/streams/juce_SubregionStream.h new file mode 100644 index 000000000..323c24921 --- /dev/null +++ b/source/modules/juce_core/streams/juce_SubregionStream.h @@ -0,0 +1,91 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SUBREGIONSTREAM_H_INCLUDED +#define JUCE_SUBREGIONSTREAM_H_INCLUDED + +#include "juce_InputStream.h" +#include "../memory/juce_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 JUCE_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() override; + int64 getPosition() override; + bool setPosition (int64 newPosition) override; + int read (void* destBuffer, int maxBytesToRead) override; + bool isExhausted() override; + +private: + //============================================================================== + OptionalScopedPointer source; + const int64 startPositionInSourceStream, lengthOfSourceStream; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubregionStream) +}; + +#endif // JUCE_SUBREGIONSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/system/juce_PlatformDefs.h b/source/modules/juce_core/system/juce_PlatformDefs.h new file mode 100644 index 000000000..b082f8ba7 --- /dev/null +++ b/source/modules/juce_core/system/juce_PlatformDefs.h @@ -0,0 +1,373 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PLATFORMDEFS_H_INCLUDED +#define JUCE_PLATFORMDEFS_H_INCLUDED + +//============================================================================== +/* This file defines miscellaneous macros for debugging, assertions, etc. +*/ + +//============================================================================== +#ifdef JUCE_FORCE_DEBUG + #undef JUCE_DEBUG + + #if JUCE_FORCE_DEBUG + #define JUCE_DEBUG 1 + #endif +#endif + +/** This macro defines the C calling convention used as the standard for Juce calls. */ +#if JUCE_MSVC + #define JUCE_CALLTYPE __stdcall + #define JUCE_CDECL __cdecl +#else + #define JUCE_CALLTYPE + #define JUCE_CDECL +#endif + +//============================================================================== +// Debugging and assertion macros + +#if JUCE_LOG_ASSERTIONS || JUCE_DEBUG + #define juce_LogCurrentAssertion juce::logAssertion (__FILE__, __LINE__); +#else + #define juce_LogCurrentAssertion +#endif + +//============================================================================== +#if JUCE_IOS || JUCE_LINUX || JUCE_ANDROID || JUCE_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 jassert() + */ + #define juce_breakDebugger { ::kill (0, SIGTRAP); } +#elif JUCE_USE_INTRINSICS + #ifndef __INTEL_COMPILER + #pragma intrinsic (__debugbreak) + #endif + #define juce_breakDebugger { __debugbreak(); } +#elif JUCE_GCC || JUCE_MAC + #if JUCE_NO_INLINE_ASM + #define juce_breakDebugger { } + #else + #define juce_breakDebugger { asm ("int $3"); } + #endif +#else + #define juce_breakDebugger { __asm int 3 } +#endif + +#if JUCE_CLANG && defined (__has_feature) && ! defined (JUCE_ANALYZER_NORETURN) + #if __has_feature (attribute_analyzer_noreturn) + inline void __attribute__((analyzer_noreturn)) juce_assert_noreturn() {} + #define JUCE_ANALYZER_NORETURN juce_assert_noreturn(); + #endif +#endif + +#ifndef JUCE_ANALYZER_NORETURN + #define JUCE_ANALYZER_NORETURN +#endif + +//============================================================================== +#if JUCE_DEBUG || DOXYGEN + /** Writes a string to the standard error stream. + This is only compiled in a debug build. + @see Logger::outputDebugString + */ + #define DBG(dbgtext) { juce::String tempDbgBuf; tempDbgBuf << dbgtext; juce::Logger::outputDebugString (tempDbgBuf); } + + //============================================================================== + /** This will always cause an assertion failure. + It is only compiled in a debug build, (unless JUCE_LOG_ASSERTIONS is enabled for your build). + @see jassert + */ + #define jassertfalse { juce_LogCurrentAssertion; if (juce::juce_isRunningUnderDebugger()) juce_breakDebugger; JUCE_ANALYZER_NORETURN } + + //============================================================================== + /** 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 jassert(expression) { if (! (expression)) jassertfalse; } + +#else + //============================================================================== + // If debugging is disabled, these dummy debug and assertion macros are used.. + + #define DBG(dbgtext) + #define jassertfalse { juce_LogCurrentAssertion } + + #if JUCE_LOG_ASSERTIONS + #define jassert(expression) { if (! (expression)) jassertfalse; } + #else + #define jassert(a) {} + #endif + +#endif + +//============================================================================== +#ifndef DOXYGEN +namespace juce +{ + template struct JuceStaticAssert; + template <> struct JuceStaticAssert { 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_jassert(expression) juce::JuceStaticAssert::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: + JUCE_DECLARE_NON_COPYABLE (MyClass) + };@endcode +*/ +#define JUCE_DECLARE_NON_COPYABLE(className) \ + className (const className&);\ + className& operator= (const className&); + +/** This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and + JUCE_LEAK_DETECTOR macro for a class. +*/ +#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className) \ + JUCE_DECLARE_NON_COPYABLE(className) \ + JUCE_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 JUCE_PREVENT_HEAP_ALLOCATION \ + private: \ + static void* operator new (size_t); \ + static void operator delete (void*); + + +//============================================================================== +#if ! DOXYGEN + #define JUCE_JOIN_MACRO_HELPER(a, b) a ## b + #define JUCE_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 JUCE_JOIN_MACRO(item1, item2) JUCE_JOIN_MACRO_HELPER (item1, item2) + +/** A handy C macro for stringifying any symbol, rather than just a macro parameter. +*/ +#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) + + +//============================================================================== +#if JUCE_CATCH_UNHANDLED_EXCEPTIONS + + #define JUCE_TRY try + + #define JUCE_CATCH_ALL catch (...) {} + #define JUCE_CATCH_ALL_ASSERT catch (...) { jassertfalse; } + + #if ! JUCE_MODULE_AVAILABLE_juce_gui_basics + #define JUCE_CATCH_EXCEPTION JUCE_CATCH_ALL + #else + /** Used in try-catch blocks, this macro will send exceptions to the JUCEApplication + object so they can be logged by the application if it wants to. + */ + #define JUCE_CATCH_EXCEPTION \ + catch (const std::exception& e) \ + { \ + juce::JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); \ + } \ + catch (...) \ + { \ + juce::JUCEApplication::sendUnhandledException (nullptr, __FILE__, __LINE__); \ + } + #endif + +#else + + #define JUCE_TRY + #define JUCE_CATCH_EXCEPTION + #define JUCE_CATCH_ALL + #define JUCE_CATCH_ALL_ASSERT + +#endif + +//============================================================================== +#if JUCE_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 JUCE_MSVC + #define forcedinline __forceinline + #else + #define forcedinline inline __attribute__((always_inline)) + #endif +#endif + +#if JUCE_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 JUCE_ALIGN(bytes) __declspec (align (bytes)) +#else + #define JUCE_ALIGN(bytes) __attribute__ ((aligned (bytes))) +#endif + +//============================================================================== +// Cross-compiler deprecation macros.. +#ifdef DOXYGEN + /** This macro can be used to wrap a function which has been deprecated. */ + #define JUCE_DEPRECATED(functionDef) + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) +#elif JUCE_MSVC && ! JUCE_NO_DEPRECATION_WARNINGS + #define JUCE_DEPRECATED(functionDef) __declspec(deprecated) functionDef + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) __declspec(deprecated) functionDef body +#elif JUCE_GCC && ! JUCE_NO_DEPRECATION_WARNINGS + #define JUCE_DEPRECATED(functionDef) functionDef __attribute__ ((deprecated)) + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) functionDef __attribute__ ((deprecated)) body +#else + #define JUCE_DEPRECATED(functionDef) functionDef + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) functionDef body +#endif + +//============================================================================== +#if JUCE_ANDROID && ! DOXYGEN + #define JUCE_MODAL_LOOPS_PERMITTED 0 +#elif ! defined (JUCE_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 JUCE_MODAL_LOOPS_PERMITTED 1 +#endif + +//============================================================================== +#if JUCE_GCC + #define JUCE_PACKED __attribute__((packed)) +#elif ! DOXYGEN + #define JUCE_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 JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif +#endif + +#if JUCE_CLANG && defined (__has_feature) + #if __has_feature (cxx_nullptr) + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #endif + + #if __has_feature (cxx_noexcept) + #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 + #endif + + #if __has_feature (cxx_rvalue_references) + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + #endif + + #ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif + + #ifndef JUCE_COMPILER_SUPPORTS_ARC + #define JUCE_COMPILER_SUPPORTS_ARC 1 + #endif +#endif + +#if defined (_MSC_VER) && _MSC_VER >= 1600 + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 +#endif + +#if defined (_MSC_VER) && _MSC_VER >= 1700 + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 +#endif + +//============================================================================== +// Declare some fake versions of nullptr and noexcept, for older compilers: +#if ! (DOXYGEN || JUCE_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 || JUCE_COMPILER_SUPPORTS_NULLPTR) + #ifdef nullptr + #undef nullptr + #endif + #define nullptr (0) +#endif + +#if ! (DOXYGEN || JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) + #undef override + #define override +#endif + +#endif // JUCE_PLATFORMDEFS_H_INCLUDED diff --git a/source/modules/juce_core/system/juce_StandardHeader.h b/source/modules/juce_core/system/juce_StandardHeader.h new file mode 100644 index 000000000..d2c880e62 --- /dev/null +++ b/source/modules/juce_core/system/juce_StandardHeader.h @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STANDARDHEADER_H_INCLUDED +#define JUCE_STANDARDHEADER_H_INCLUDED + +//============================================================================== +/** Current JUCE version number. + + See also SystemStats::getJUCEVersion() for a string version. +*/ +#define JUCE_MAJOR_VERSION 2 +#define JUCE_MINOR_VERSION 1 +#define JUCE_BUILDNUMBER 2 + +/** Current Juce version number. + + Bits 16 to 32 = major version. + Bits 8 to 16 = minor version. + Bits 0 to 8 = point release. + + See also SystemStats::getJUCEVersion() for a string version. +*/ +#define JUCE_VERSION ((JUCE_MAJOR_VERSION << 16) + (JUCE_MINOR_VERSION << 8) + JUCE_BUILDNUMBER) + + +//============================================================================== +#include "juce_TargetPlatform.h" // (sets up the various JUCE_WINDOWS, JUCE_MAC, etc flags) +#include "juce_PlatformDefs.h" + +//============================================================================== +// Now we'll include some common OS headers.. +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4245 4100) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if JUCE_USE_INTRINSICS + #include +#endif + +#if JUCE_MAC || JUCE_IOS + #include +#endif + +#if JUCE_LINUX + #include + + #if __INTEL_COMPILER + #if __ia64__ + #include + #else + #include + #endif + #endif +#endif + +#if JUCE_MSVC && JUCE_DEBUG + #include +#endif + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#if JUCE_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 JUCE_MSVC + #ifdef JUCE_DLL_BUILD + #define JUCE_API __declspec (dllexport) + #pragma warning (disable: 4251) + #elif defined (JUCE_DLL) + #define JUCE_API __declspec (dllimport) + #pragma warning (disable: 4251) + #endif + #ifdef __INTEL_COMPILER + #pragma warning (disable: 1125) // (virtual override warning) + #endif +#elif defined (JUCE_DLL) || defined (JUCE_DLL_BUILD) + #define JUCE_API __attribute__ ((visibility("default"))) +#endif + +//============================================================================== +#ifndef JUCE_API + #define JUCE_API /**< This macro is added to all juce public class declarations. */ +#endif + +#if JUCE_MSVC && JUCE_DLL_BUILD + #define JUCE_PUBLIC_IN_DLL_BUILD(declaration) public: declaration; private: +#else + #define JUCE_PUBLIC_IN_DLL_BUILD(declaration) declaration; +#endif + +/** This macro is added to all juce public function declarations. */ +#define JUCE_PUBLIC_FUNCTION JUCE_API JUCE_CALLTYPE + +#if (! defined (JUCE_CATCH_DEPRECATED_CODE_MISUSE)) && JUCE_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 JUCE_CATCH_DEPRECATED_CODE_MISUSE 1 +#endif + +#ifndef DOXYGEN + #define JUCE_NAMESPACE juce // This old macro is deprecated: you should just use the juce namespace directly. +#endif + +//============================================================================== +// Now include some common headers... +namespace juce +{ + extern JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger(); + extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noexcept; + + #include "../memory/juce_Memory.h" + #include "../maths/juce_MathsFunctions.h" + #include "../memory/juce_ByteOrder.h" + #include "../logging/juce_Logger.h" + #include "../memory/juce_LeakedObjectDetector.h" +} + +#endif // JUCE_STANDARDHEADER_H_INCLUDED diff --git a/source/modules/juce_core/system/juce_SystemStats.cpp b/source/modules/juce_core/system/juce_SystemStats.cpp new file mode 100644 index 000000000..365e94d9f --- /dev/null +++ b/source/modules/juce_core/system/juce_SystemStats.cpp @@ -0,0 +1,183 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +String SystemStats::getJUCEVersion() +{ + // 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_jassert (sizeof (pointer_sized_int) == sizeof (void*)); + static_jassert (sizeof (int8) == 1); + static_jassert (sizeof (uint8) == 1); + static_jassert (sizeof (int16) == 2); + static_jassert (sizeof (uint16) == 2); + static_jassert (sizeof (int32) == 4); + static_jassert (sizeof (uint32) == 4); + static_jassert (sizeof (int64) == 8); + static_jassert (sizeof (uint64) == 8); + + return "JUCE v" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) + "." JUCE_STRINGIFY(JUCE_MINOR_VERSION) + "." JUCE_STRINGIFY(JUCE_BUILDNUMBER); +} + +#if JUCE_ANDROID && ! defined (JUCE_DISABLE_JUCE_VERSION_PRINTING) + #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 +#endif + +#if JUCE_DEBUG && ! JUCE_DISABLE_JUCE_VERSION_PRINTING + struct JuceVersionPrinter + { + JuceVersionPrinter() + { + DBG (SystemStats::getJUCEVersion()); + } + }; + + static JuceVersionPrinter juceVersionPrinter; +#endif + + +//============================================================================== +struct CPUInformation +{ + CPUInformation() noexcept + : numCpus (0), hasMMX (false), hasSSE (false), + hasSSE2 (false), hasSSE3 (false), has3DNow (false) + { + initialise(); + } + + void initialise() noexcept; + + int numCpus; + bool hasMMX, hasSSE, hasSSE2, hasSSE3, has3DNow; +}; + +static const CPUInformation& getCPUInformation() noexcept +{ + static CPUInformation info; + return info; +} + +int SystemStats::getNumCpus() noexcept { return getCPUInformation().numCpus; } +bool SystemStats::hasMMX() noexcept { return getCPUInformation().hasMMX; } +bool SystemStats::hasSSE() noexcept { return getCPUInformation().hasSSE; } +bool SystemStats::hasSSE2() noexcept { return getCPUInformation().hasSSE2; } +bool SystemStats::hasSSE3() noexcept { return getCPUInformation().hasSSE3; } +bool SystemStats::has3DNow() noexcept { return getCPUInformation().has3DNow; } + + +//============================================================================== +String SystemStats::getStackBacktrace() +{ + String result; + + #if JUCE_ANDROID || JUCE_MINGW + jassertfalse; // sorry, not implemented yet! + + #elif JUCE_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 JUCE_WINDOWS +static LONG WINAPI handleCrash (LPEXCEPTION_POINTERS) +{ + globalCrashHandler(); + return EXCEPTION_EXECUTE_HANDLER; +} +#else +static void handleCrash (int) +{ + globalCrashHandler(); + kill (getpid(), SIGKILL); +} + +int juce_siginterrupt (int sig, int flag); +#endif + +void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler) +{ + jassert (handler != nullptr); // This must be a valid function. + globalCrashHandler = handler; + + #if JUCE_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); + juce_siginterrupt (signals[i], 1); + } + #endif +} diff --git a/source/modules/juce_core/system/juce_SystemStats.h b/source/modules/juce_core/system/juce_SystemStats.h new file mode 100644 index 000000000..838f68dfe --- /dev/null +++ b/source/modules/juce_core/system/juce_SystemStats.h @@ -0,0 +1,188 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SYSTEMSTATS_H_INCLUDED +#define JUCE_SYSTEMSTATS_H_INCLUDED + +#include "../text/juce_StringArray.h" + + +//============================================================================== +/** + Contains methods for finding out about the current hardware and OS configuration. +*/ +class JUCE_API SystemStats +{ +public: + //============================================================================== + /** Returns the current version of JUCE, + See also the JUCE_VERSION, JUCE_MAJOR_VERSION and JUCE_MINOR_VERSION macros. + */ + static String getJUCEVersion(); + + //============================================================================== + /** 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 CPU cores. */ + static int getNumCpus() noexcept; + + /** 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(); + + static bool hasMMX() noexcept; /**< Returns true if Intel MMX instructions are available. */ + static bool hasSSE() noexcept; /**< Returns true if Intel SSE instructions are available. */ + static bool hasSSE2() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ + static bool hasSSE3() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ + static bool has3DNow() noexcept; /**< Returns true if AMD 3DNOW instructions are available. */ + + //============================================================================== + /** 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: + //============================================================================== + SystemStats(); + + JUCE_DECLARE_NON_COPYABLE (SystemStats) +}; + + +#endif // JUCE_SYSTEMSTATS_H_INCLUDED diff --git a/source/modules/juce_core/system/juce_TargetPlatform.h b/source/modules/juce_core/system/juce_TargetPlatform.h new file mode 100644 index 000000000..f6169dd82 --- /dev/null +++ b/source/modules/juce_core/system/juce_TargetPlatform.h @@ -0,0 +1,205 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TARGETPLATFORM_H_INCLUDED +#define JUCE_TARGETPLATFORM_H_INCLUDED + +//============================================================================== +/* 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 JUCE_WINDOWS, JUCE_MAC JUCE_LINUX, JUCE_IOS, JUCE_ANDROID, etc. + - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. + - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. + - Either JUCE_INTEL or JUCE_PPC + - Either JUCE_GCC or JUCE_MSVC +*/ + +//============================================================================== +#if (defined (_WIN32) || defined (_WIN64)) + #define JUCE_WIN32 1 + #define JUCE_WINDOWS 1 +#elif defined (JUCE_ANDROID) + #undef JUCE_ANDROID + #define JUCE_ANDROID 1 +#elif defined (LINUX) || defined (__linux__) + #define JUCE_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 JUCE_IPHONE 1 + #define JUCE_IOS 1 + #else + #define JUCE_MAC 1 + #endif +#elif defined (__FreeBSD__) + #define JUCE_BSD 1 +#else + #error "Unknown platform!" +#endif + +//============================================================================== +#if JUCE_WINDOWS + #ifdef _MSC_VER + #ifdef _WIN64 + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + #endif + + #ifdef _DEBUG + #define JUCE_DEBUG 1 + #endif + + #ifdef __MINGW32__ + #define JUCE_MINGW 1 + #ifdef __MINGW64__ + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + #endif + + /** If defined, this indicates that the processor is little-endian. */ + #define JUCE_LITTLE_ENDIAN 1 + + #define JUCE_INTEL 1 +#endif + +//============================================================================== +#if JUCE_MAC || JUCE_IOS + + #if defined (DEBUG) || defined (_DEBUG) || ! (defined (NDEBUG) || defined (_NDEBUG)) + #define JUCE_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 JUCE_LITTLE_ENDIAN 1 + #else + #define JUCE_BIG_ENDIAN 1 + #endif +#endif + +#if JUCE_MAC + + #if defined (__ppc__) || defined (__ppc64__) + #define JUCE_PPC 1 + #elif defined (__arm__) + #define JUCE_ARM 1 + #else + #define JUCE_INTEL 1 + #endif + + #ifdef __LP64__ + #define JUCE_64BIT 1 + #else + #define JUCE_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 JUCE_LINUX || JUCE_ANDROID + + #ifdef _DEBUG + #define JUCE_DEBUG 1 + #endif + + // Allow override for big-endian Linux platforms + #if defined (__LITTLE_ENDIAN__) || ! defined (JUCE_BIG_ENDIAN) + #define JUCE_LITTLE_ENDIAN 1 + #undef JUCE_BIG_ENDIAN + #else + #undef JUCE_LITTLE_ENDIAN + #define JUCE_BIG_ENDIAN 1 + #endif + + #if defined (__LP64__) || defined (_LP64) + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + + #ifdef __arm__ + #define JUCE_ARM 1 + #elif __MMX__ || __SSE__ || __amd64__ + #define JUCE_INTEL 1 + #endif +#endif + +//============================================================================== +// Compiler type macros. + +#ifdef __clang__ + #define JUCE_CLANG 1 + #define JUCE_GCC 1 +#elif defined (__GNUC__) + #define JUCE_GCC 1 +#elif defined (_MSC_VER) + #define JUCE_MSVC 1 + + #if _MSC_VER < 1500 + #define JUCE_VC8_OR_EARLIER 1 + + #if _MSC_VER < 1400 + #define JUCE_VC7_OR_EARLIER 1 + + #if _MSC_VER < 1300 + #warning "MSVC 6.0 is no longer supported!" + #endif + #endif + #endif + + #if JUCE_64BIT || ! JUCE_VC7_OR_EARLIER + #define JUCE_USE_INTRINSICS 1 + #endif +#else + #error unknown compiler +#endif + +#endif // JUCE_TARGETPLATFORM_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_CharPointer_ASCII.h b/source/modules/juce_core/text/juce_CharPointer_ASCII.h new file mode 100644 index 000000000..a966543b5 --- /dev/null +++ b/source/modules/juce_core/text/juce_CharPointer_ASCII.h @@ -0,0 +1,387 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_ASCII_H_INCLUDED +#define JUCE_CHARPOINTER_ASCII_H_INCLUDED + + +//============================================================================== +/** + 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 juce_wchar operator*() const noexcept { return (juce_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 juce_wchar getAndAdvance() noexcept { return (juce_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 juce_wchar operator[] (const int characterIndex) const noexcept + { + return (juce_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 juce_wchar charToWrite) noexcept + { + *data++ = (char) charToWrite; + } + + inline void replaceChar (const juce_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 juce_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 JUCE_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 juce_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 juce_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 ((juce_wchar) (uint8) *data) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const { return CharacterFunctions::isLowerCase ((juce_wchar) (uint8) *data) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase ((juce_wchar) (uint8) *data); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase ((juce_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 JUCE_LINUX || JUCE_ANDROID + return atoll (data); + #elif JUCE_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 (juce_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 // JUCE_CHARPOINTER_ASCII_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_CharPointer_UTF16.h b/source/modules/juce_core/text/juce_CharPointer_UTF16.h new file mode 100644 index 000000000..c4de13bc9 --- /dev/null +++ b/source/modules/juce_core/text/juce_CharPointer_UTF16.h @@ -0,0 +1,501 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF16_H_INCLUDED +#define JUCE_CHARPOINTER_UTF16_H_INCLUDED + + +//============================================================================== +/** + 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 JUCE_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. */ + juce_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 (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF16 operator++() noexcept + { + const juce_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 juce_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. */ + juce_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 (juce_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. */ + juce_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 (juce_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 juce_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; + juce_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 JUCE_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 juce_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 juce_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. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept + { + #if JUCE_WINDOWS + return _wtoi (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if JUCE_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 (juce_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 // JUCE_CHARPOINTER_UTF16_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_CharPointer_UTF32.h b/source/modules/juce_core/text/juce_CharPointer_UTF32.h new file mode 100644 index 000000000..7a24b9644 --- /dev/null +++ b/source/modules/juce_core/text/juce_CharPointer_UTF32.h @@ -0,0 +1,378 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF32_H_INCLUDED +#define JUCE_CHARPOINTER_UTF32_H_INCLUDED + + +//============================================================================== +/** + 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 juce_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 juce_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 juce_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 juce_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 juce_wchar charToWrite) noexcept + { + *data++ = charToWrite; + } + + inline void replaceChar (const juce_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 JUCE_NATIVE_WCHAR_IS_UTF32 && ! JUCE_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 juce_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 JUCE_NATIVE_WCHAR_IS_UTF32 && ! JUCE_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 juce_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 juce_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. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (*data); } + /** Returns a lower-case version of the first character of this string. */ + juce_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 (juce_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 // JUCE_CHARPOINTER_UTF32_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_CharPointer_UTF8.h b/source/modules/juce_core/text/juce_CharPointer_UTF8.h new file mode 100644 index 000000000..25d004077 --- /dev/null +++ b/source/modules/juce_core/text/juce_CharPointer_UTF8.h @@ -0,0 +1,565 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF8_H_INCLUDED +#define JUCE_CHARPOINTER_UTF8_H_INCLUDED + +//============================================================================== +/** + 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. */ + juce_wchar operator*() const noexcept + { + const signed char byte = (signed char) *data; + + if (byte >= 0) + return (juce_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 (juce_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) + { + juce_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. */ + juce_wchar getAndAdvance() noexcept + { + const signed char byte = (signed char) *data++; + + if (byte >= 0) + return (juce_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 (juce_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. */ + juce_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 + { + jassert (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 juce_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; + juce_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 juce_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 JUCE_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 juce_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 juce_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. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + juce_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 JUCE_LINUX || JUCE_ANDROID + return atoll (data); + #elif JUCE_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 (juce_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 // JUCE_CHARPOINTER_UTF8_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_CharacterFunctions.cpp b/source/modules/juce_core/text/juce_CharacterFunctions.cpp new file mode 100644 index 000000000..11eaa188b --- /dev/null +++ b/source/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -0,0 +1,154 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +//============================================================================== +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) noexcept +{ + return towupper ((wchar_t) character); +} + +juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) noexcept +{ + return towlower ((wchar_t) character); +} + +bool CharacterFunctions::isUpperCase (const juce_wchar character) noexcept +{ + #if JUCE_WINDOWS + return iswupper ((wchar_t) character) != 0; + #else + return toLowerCase (character) != character; + #endif +} + +bool CharacterFunctions::isLowerCase (const juce_wchar character) noexcept +{ + #if JUCE_WINDOWS + return iswlower ((wchar_t) character) != 0; + #else + return toUpperCase (character) != character; + #endif +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +bool CharacterFunctions::isWhitespace (const char character) noexcept +{ + return character == ' ' || (character <= 13 && character >= 9); +} + +bool CharacterFunctions::isWhitespace (const juce_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 juce_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 juce_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 juce_wchar character) noexcept +{ + return iswalnum ((wchar_t) character) != 0; +} + +int CharacterFunctions::getHexDigitValue (const juce_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/source/modules/juce_core/text/juce_CharacterFunctions.h b/source/modules/juce_core/text/juce_CharacterFunctions.h new file mode 100644 index 000000000..d2cdc1208 --- /dev/null +++ b/source/modules/juce_core/text/juce_CharacterFunctions.h @@ -0,0 +1,587 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARACTERFUNCTIONS_H_INCLUDED +#define JUCE_CHARACTERFUNCTIONS_H_INCLUDED + + +//============================================================================== +#if JUCE_WINDOWS && ! DOXYGEN + #define JUCE_NATIVE_WCHAR_IS_UTF8 0 + #define JUCE_NATIVE_WCHAR_IS_UTF16 1 + #define JUCE_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 JUCE_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 JUCE_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 JUCE_NATIVE_WCHAR_IS_UTF32 1 +#endif + +#if JUCE_NATIVE_WCHAR_IS_UTF32 || DOXYGEN + /** A platform-independent 32-bit unicode character type. */ + typedef wchar_t juce_wchar; +#else + typedef uint32 juce_wchar; +#endif + +#ifndef DOXYGEN + /** This macro is deprecated, but preserved for compatibility with old code. */ + #define JUCE_T(stringLiteral) (L##stringLiteral) +#endif + +#if JUCE_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 + JUCE_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) JUCE_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 JUCE_API CharacterFunctions +{ +public: + //============================================================================== + /** Converts a character to upper-case. */ + static juce_wchar toUpperCase (juce_wchar character) noexcept; + /** Converts a character to lower-case. */ + static juce_wchar toLowerCase (juce_wchar character) noexcept; + + /** Checks whether a unicode character is upper-case. */ + static bool isUpperCase (juce_wchar character) noexcept; + /** Checks whether a unicode character is lower-case. */ + static bool isLowerCase (juce_wchar character) noexcept; + + /** Checks whether a character is whitespace. */ + static bool isWhitespace (char character) noexcept; + /** Checks whether a character is whitespace. */ + static bool isWhitespace (juce_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 (juce_wchar character) noexcept; + + /** Checks whether a character is alphabetic. */ + static bool isLetter (char character) noexcept; + /** Checks whether a character is alphabetic. */ + static bool isLetter (juce_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 (juce_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 (juce_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(); + juce_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 juce_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 juce_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 juce_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 juce_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 juce_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, juce_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 (Type text) noexcept + { + while (text.isWhitespace()) + ++text; + + return text; + } + + /** Returns a pointer to the first character in the string which is found in + the breakCharacters string. + */ + template + static Type findEndOfToken (Type text, const Type breakCharacters, const Type quoteCharacters) + { + juce_wchar currentQuoteChar = 0; + + while (! text.isEmpty()) + { + const juce_wchar c = text.getAndAdvance(); + + if (currentQuoteChar == 0 && breakCharacters.indexOf (c) >= 0) + { + --text; + break; + } + + if (quoteCharacters.indexOf (c) >= 0) + { + if (currentQuoteChar == 0) + currentQuoteChar = c; + else if (currentQuoteChar == c) + currentQuoteChar = 0; + } + } + + return text; + } + +private: + static double mulexp10 (const double value, int exponent) noexcept; +}; + + +#endif // JUCE_CHARACTERFUNCTIONS_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_Identifier.cpp b/source/modules/juce_core/text/juce_Identifier.cpp new file mode 100644 index 000000000..c52ad5f24 --- /dev/null +++ b/source/modules/juce_core/text/juce_Identifier.cpp @@ -0,0 +1,74 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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.. */ + jassert (isValidIdentifier (toString())); +} + +Identifier::~Identifier() +{ +} + +Identifier Identifier::null; + +bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept +{ + return possibleIdentifier.isNotEmpty() + && possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%"); +} diff --git a/source/modules/juce_core/text/juce_Identifier.h b/source/modules/juce_core/text/juce_Identifier.h new file mode 100644 index 000000000..8ab1fd81f --- /dev/null +++ b/source/modules/juce_core/text/juce_Identifier.h @@ -0,0 +1,111 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_IDENTIFIER_H_INCLUDED +#define JUCE_IDENTIFIER_H_INCLUDED + +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 JUCE_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 // JUCE_IDENTIFIER_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_LocalisedStrings.cpp b/source/modules/juce_core/text/juce_LocalisedStrings.cpp new file mode 100644 index 000000000..553474245 --- /dev/null +++ b/source/modules/juce_core/text/juce_LocalisedStrings.cpp @@ -0,0 +1,187 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_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) + { + juce_wchar lastChar = 0; + String::CharPointerType t (text.getCharPointer() + startPos); + + for (;;) + { + const juce_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 juce::translate (text); +} + +String LocalisedStrings::translateWithCurrentMappings (const char* text) +{ + return juce::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/source/modules/juce_core/text/juce_LocalisedStrings.h b/source/modules/juce_core/text/juce_LocalisedStrings.h new file mode 100644 index 000000000..0781a8a91 --- /dev/null +++ b/source/modules/juce_core/text/juce_LocalisedStrings.h @@ -0,0 +1,224 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LOCALISEDSTRINGS_H_INCLUDED +#define JUCE_LOCALISEDSTRINGS_H_INCLUDED + +#include "juce_StringPairArray.h" +#include "../files/juce_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 Juce classes themselves, so your application has a chance to + intercept and translate any internal Juce text strings that might be shown. (You can easily + get a list of all the messages by searching for the TRANS() macro in the Juce source + code). +*/ +class JUCE_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); + + JUCE_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) juce::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 // JUCE_LOCALISEDSTRINGS_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_NewLine.h b/source/modules/juce_core/text/juce_NewLine.h new file mode 100644 index 000000000..7dcb6ab6c --- /dev/null +++ b/source/modules/juce_core/text/juce_NewLine.h @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NEWLINE_H_INCLUDED +#define JUCE_NEWLINE_H_INCLUDED + + +//============================================================================== +/** 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 JUCE_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 +*/ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&); + + +#endif // JUCE_NEWLINE_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_String.cpp b/source/modules/juce_core/text/juce_String.cpp new file mode 100644 index 000000000..56c3926b8 --- /dev/null +++ b/source/modules/juce_core/text/juce_String.cpp @@ -0,0 +1,2396 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +NewLine newLine; + +#if defined (JUCE_STRINGS_ARE_UNICODE) && ! JUCE_STRINGS_ARE_UNICODE + #error "JUCE_STRINGS_ARE_UNICODE is deprecated! All strings are now unicode by default." +#endif + +#if JUCE_NATIVE_WCHAR_IS_UTF8 + typedef CharPointer_UTF8 CharPointer_wchar_t; +#elif JUCE_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) (reinterpret_cast (end.getAddress()) + - reinterpret_cast (start.getAddress())); + const CharPointerType dest (createUninitialisedBytes (numBytes + sizeof (CharType))); + memcpy (dest.getAddress(), start, numBytes); + dest.getAddress()[numBytes / sizeof (CharType)] = 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 (jmax (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 JUCE_NATIVE_WCHAR_IS_UTF8 + static_jassert (sizeof (wchar_t) == 1); + #elif JUCE_NATIVE_WCHAR_IS_UTF16 + static_jassert (sizeof (wchar_t) == 2); + #elif JUCE_NATIVE_WCHAR_IS_UTF32 + static_jassert (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 JUCE_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. + */ + jassert (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. + */ + jassert (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 juce_wchar character) +{ + String result (PreallocationBytes (CharPointerType::getBytesRequiredFor (character))); + CharPointerType t (result.text); + t.write (character); + t.writeNull(); + return result; +} + +//============================================================================== +namespace NumberToStringConverters +{ + template + static char* printDigits (char* t, Type v) noexcept + { + *--t = 0; + + do + { + *--t = '0' + (char) (v % 10); + v /= 10; + + } while (v > 0); + + return t; + } + + // pass in a pointer to the END of a buffer.. + static char* numberToString (char* t, const int64 n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + // NB: this needs to be careful not to call -std::numeric_limits::min(), + // which has undefined behaviour + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, uint64 v) noexcept + { + return printDigits (t, v); + } + + static char* numberToString (char* t, const int n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + // NB: this needs to be careful not to call -std::numeric_limits::min(), + // which has undefined behaviour + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, unsigned int v) noexcept + { + return printDigits (t, v); + } + + 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 JUCE_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 JUCE_MAC || JUCE_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()); +} + +juce_wchar String::operator[] (int index) const noexcept +{ + jassert (index == 0 || (index > 0 && index <= (int) text.lengthUpTo ((size_t) index + 1))); + return text [index]; +} + +int String::hashCode() const noexcept +{ + int result = 0; + + for (CharPointerType t (text); ! t.isEmpty();) + result = 31 * result + (int) t.getAndAdvance(); + + return result; +} + +int64 String::hashCode64() const noexcept +{ + int64 result = 0; + + for (CharPointerType t (text); ! t.isEmpty();) + result = 101 * result + t.getAndAdvance(); + + return result; +} + +//============================================================================== +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const char* const s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const wchar_t* const s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF16 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF32 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const String& s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const char* const s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const wchar_t* const s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF16 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF32 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator> (const String& s1, const String& s2) noexcept { return s1.compare (s2) > 0; } +JUCE_API bool JUCE_CALLTYPE operator< (const String& s1, const String& s2) noexcept { return s1.compare (s2) < 0; } +JUCE_API bool JUCE_CALLTYPE operator>= (const String& s1, const String& s2) noexcept { return s1.compare (s2) >= 0; } +JUCE_API bool JUCE_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. + */ + jassert (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 ! JUCE_NATIVE_WCHAR_IS_UTF32 +String& String::operator+= (const juce_wchar ch) +{ + const juce_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; +} + +//============================================================================== +JUCE_API String JUCE_CALLTYPE operator+ (const char* const string1, const String& string2) +{ + String s (string1); + return s += string2; +} + +JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* const string1, const String& string2) +{ + String s (string1); + return s += string2; +} + +JUCE_API String JUCE_CALLTYPE operator+ (const char s1, const String& s2) { return String::charToString ((juce_wchar) (uint8) s1) + s2; } +JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t s1, const String& s2) { return String::charToString (s1) + s2; } +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +JUCE_API String JUCE_CALLTYPE operator+ (const juce_wchar s1, const String& s2) { return String::charToString (s1) + s2; } +#endif + +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const String& s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char* const s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const wchar_t* s2) { return s1 += s2; } + +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const wchar_t s2) { return s1 += s2; } +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const juce_wchar s2) { return s1 += s2; } +#endif + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t s2) { return s1 += s2; } +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const juce_wchar s2) { return s1 += s2; } +#endif + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char* const s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t* const s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const String& s2) { return s1 += s2; } + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += (int) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int64 number) { return s1 << String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& text) +{ + const size_t numBytes = text.getNumBytesAsUTF8(); + + #if (JUCE_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; +} + +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&) +{ + return string1 += NewLine::getDefault(); +} + +//============================================================================== +int String::indexOfChar (const juce_wchar character) const noexcept +{ + return text.indexOf (character); +} + +int String::indexOfChar (const int startIndex, const juce_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 juce_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 juce_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 juce_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 juce_wchar wc, const juce_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 juce_wchar padCharacter, int minimumLength) const +{ + jassert (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 juce_wchar padCharacter, int minimumLength) const +{ + jassert (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 s) + : source (s), dest (nullptr), allocatedBytes (StringHolder::getAllocatedNumBytes (s)), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + void write (juce_wchar c) + { + bytesWritten += String::CharPointerType::getBytesRequiredFor (c); + + if (bytesWritten > allocatedBytes) + { + allocatedBytes += jmax ((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 juce_wchar charToReplace, const juce_wchar charToInsert) const +{ + if (! containsChar (charToReplace)) + return *this; + + StringCreationHelper builder (text); + + for (;;) + { + juce_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 (;;) + { + juce_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 juce_wchar character) const noexcept +{ + jassert (character != 0); // strings can't contain a null character! + + return *text == character; +} + +bool String::endsWithChar (const juce_wchar character) const noexcept +{ + jassert (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 juce_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 juce_wchar c = builder.source.toLowerCase(); + ++(builder.source); + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +//============================================================================== +juce_wchar String::getLastCharacter() const noexcept +{ + return isEmpty() ? juce_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) jmax (0, length() - numberToDrop)); +} + +String String::getLastCharacters (const int numCharacters) const +{ + return String (text + jmax (0, length() - jmax (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 juce_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 juce_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 (;;) + { + juce_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 (;;) + { + juce_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 JUCE_WINDOWS + HeapBlock temp (bufferSize); + const int num = (int) _vsnwprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); + #elif JUCE_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 ((juce_wchar) hexDigits [nextByte >> 4]); + dest.write ((juce_wchar) hexDigits [nextByte & 0xf]); + + if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) + dest.write ((juce_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 ((juce_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 ((juce_wchar) ByteOrder::swapIfLittleEndian (src[i])); + } + else + { + for (int i = 0; i < numChars; ++i) + builder.write ((juce_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 juce_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 JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) + const size_t bytesToClear = (size_t) jmin ((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) + { + jassert (((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 JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +//============================================================================== +#if JUCE_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() + { + juce_wchar buffer[50] = { 0 }; + Random r; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_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 (String (std::numeric_limits::max()).getIntValue() == std::numeric_limits::max()); + expect (String (std::numeric_limits::min()).getIntValue() == std::numeric_limits::min()); + expect (String (std::numeric_limits::max()).getLargeIntValue() == std::numeric_limits::max()); + expect (String (std::numeric_limits::min()).getLargeIntValue() == std::numeric_limits::min()); + 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/source/modules/juce_core/text/juce_String.h b/source/modules/juce_core/text/juce_String.h new file mode 100644 index 000000000..e1012a5c8 --- /dev/null +++ b/source/modules/juce_core/text/juce_String.h @@ -0,0 +1,1351 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRING_H_INCLUDED +#define JUCE_STRING_H_INCLUDED + +#include "juce_CharacterFunctions.h" + +#ifndef JUCE_STRING_UTF_TYPE + #define JUCE_STRING_UTF_TYPE 8 +#endif + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +#include "../memory/juce_Atomic.h" +#include "juce_CharPointer_UTF8.h" +#include "juce_CharPointer_UTF16.h" +#include "juce_CharPointer_UTF32.h" +#include "juce_CharPointer_ASCII.h" + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +class OutputStream; + +//============================================================================== +/** + The JUCE 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 JUCE_API String +{ +public: + //============================================================================== + /** Creates an empty string. + @see empty + */ + String() noexcept; + + /** Creates a copy of another string. */ + String (const String& other) noexcept; + + #if JUCE_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 (juce_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 JUCE_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 (JUCE_STRING_UTF_TYPE == 32) + typedef CharPointer_UTF32 CharPointerType; + #elif (JUCE_STRING_UTF_TYPE == 16) + typedef CharPointer_UTF16 CharPointerType; + #elif (JUCE_STRING_UTF_TYPE == 8) + typedef CharPointer_UTF8 CharPointerType; + #else + #error "You must set the value of JUCE_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 JUCE_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 ! JUCE_NATIVE_WCHAR_IS_UTF32 + /** Appends a character at the end of this string. */ + String& operator+= (juce_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 (juce_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 (juce_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 (juce_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 (juce_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, juce_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 (juce_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 + */ + juce_wchar operator[] (int index) const noexcept; + + /** Returns the final character of the string. + If the string is empty this will return 0. + */ + juce_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 (juce_wchar characterToReplace, + juce_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 (juce_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 (juce_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 (juce_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 JUCE_MAC || JUCE_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; + JUCE_DEPRECATED (String (const String&, size_t)); + + // 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. */ +JUCE_API String JUCE_CALLTYPE operator+ (const char* string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (char string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (wchar_t string1, const String& string2); +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (juce_wchar string1, const String& string2); +#endif + +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const char* string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const wchar_t* string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, char characterToAppend); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, wchar_t characterToAppend); +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, juce_wchar characterToAppend); +#endif + +//============================================================================== +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, char characterToAppend); +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, wchar_t characterToAppend); +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, juce_wchar characterToAppend); +#endif + +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const char* string2); +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const wchar_t* string2); +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const String& string2); + +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, short number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, long number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int64 number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, float number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, double number); + +//============================================================================== +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const wchar_t* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF8 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF16 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF32 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const wchar_t* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF8 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF16 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF32 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator> (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator< (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator>= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator<= (const String& string1, const String& string2) noexcept; + +//============================================================================== +/** This operator allows you to write a juce String directly to std output streams. + This is handy for writing strings to std::cout, std::cerr, etc. +*/ +template +std::basic_ostream & JUCE_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toRawUTF8(); +} + +/** This operator allows you to write a juce String directly to std output streams. + This is handy for writing strings to std::wcout, std::wcerr, etc. +*/ +template +std::basic_ostream & JUCE_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toWideCharPointer(); +} + +/** Writes a string to an OutputStream as UTF8. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& stringToWrite); + + +#endif // JUCE_STRING_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_StringArray.cpp b/source/modules/juce_core/text/juce_StringArray.cpp new file mode 100644 index 000000000..d02da0fab --- /dev/null +++ b/source/modules/juce_core/text/juce_StringArray.cpp @@ -0,0 +1,520 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +StringArray::StringArray() noexcept +{ +} + +StringArray::StringArray (const StringArray& other) + : strings (other.strings) +{ +} + +#if JUCE_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 JUCE_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.swapWith (other.strings); +} + +void StringArray::clear() +{ + strings.clear(); +} + +void StringArray::clearQuick() +{ + strings.clearQuick(); +} + +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 +{ + jassert (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() + : jmin (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::ensureStorageAllocated (int minNumElements) +{ + strings.ensureStorageAllocated (minNumElements); +} + +void StringArray::minimiseStorageOverheads() +{ + strings.minimiseStorageOverheads(); +} diff --git a/source/modules/juce_core/text/juce_StringArray.h b/source/modules/juce_core/text/juce_StringArray.h new file mode 100644 index 000000000..f39c9feec --- /dev/null +++ b/source/modules/juce_core/text/juce_StringArray.h @@ -0,0 +1,422 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGARRAY_H_INCLUDED +#define JUCE_STRINGARRAY_H_INCLUDED + +#include "juce_String.h" +#include "../containers/juce_Array.h" + + +//============================================================================== +/** + A special array for holding a list of strings. + + @see String, StringPairArray +*/ +class JUCE_API StringArray +{ +public: + //============================================================================== + /** Creates an empty string array */ + StringArray() noexcept; + + /** Creates a copy of another string array */ + StringArray (const StringArray& other); + + #if JUCE_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 JUCE_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 all elements from the array without freeing the array's allocated storage. + @see clear + */ + void clearQuick(); + + /** 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); + + //============================================================================== + /** 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 (int minNumElements); + + /** 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; + + JUCE_LEAK_DETECTOR (StringArray) +}; + + +#endif // JUCE_STRINGARRAY_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_StringPairArray.cpp b/source/modules/juce_core/text/juce_StringPairArray.cpp new file mode 100644 index 000000000..f9cb60fe6 --- /dev/null +++ b/source/modules/juce_core/text/juce_StringPairArray.cpp @@ -0,0 +1,142 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/text/juce_StringPairArray.h b/source/modules/juce_core/text/juce_StringPairArray.h new file mode 100644 index 000000000..b6ace332b --- /dev/null +++ b/source/modules/juce_core/text/juce_StringPairArray.h @@ -0,0 +1,163 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGPAIRARRAY_H_INCLUDED +#define JUCE_STRINGPAIRARRAY_H_INCLUDED + +#include "juce_StringArray.h" + + +//============================================================================== +/** + A container for holding a set of strings which are keyed by another string. + + @see StringArray +*/ +class JUCE_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; + + JUCE_LEAK_DETECTOR (StringPairArray) +}; + + +#endif // JUCE_STRINGPAIRARRAY_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_StringPool.cpp b/source/modules/juce_core/text/juce_StringPool.cpp new file mode 100644 index 000000000..672b3025a --- /dev/null +++ b/source/modules/juce_core/text/juce_StringPool.cpp @@ -0,0 +1,114 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) + { + jassert (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/source/modules/juce_core/text/juce_StringPool.h b/source/modules/juce_core/text/juce_StringPool.h new file mode 100644 index 000000000..d4aa05823 --- /dev/null +++ b/source/modules/juce_core/text/juce_StringPool.h @@ -0,0 +1,95 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGPOOL_H_INCLUDED +#define JUCE_STRINGPOOL_H_INCLUDED + +#include "juce_String.h" +#include "../containers/juce_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 JUCE_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 // JUCE_STRINGPOOL_H_INCLUDED diff --git a/source/modules/juce_core/text/juce_TextDiff.cpp b/source/modules/juce_core/text/juce_TextDiff.cpp new file mode 100644 index 000000000..da1a47fd4 --- /dev/null +++ b/source/modules/juce_core/text/juce_TextDiff.cpp @@ -0,0 +1,246 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 = jmax (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 loopsWithoutImprovement = 0; + int bestLength = 0; + indexInA = indexInB = 0; + + for (int i = 0; i < lenA; ++i) + { + const juce_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) + { + loopsWithoutImprovement = 0; + bestLength = len; + indexInA = i; + indexInB = j; + } + } + } + + if (++loopsWithoutImprovement > 100) + break; + + 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 JUCE_UNIT_TESTS + +class DiffTests : public UnitTest +{ +public: + DiffTests() : UnitTest ("TextDiff class") {} + + static String createString() + { + juce_wchar buffer[50] = { 0 }; + Random r; + + for (int i = r.nextInt (49); --i >= 0;) + { + if (r.nextInt (10) == 0) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_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/source/modules/juce_core/text/juce_TextDiff.h b/source/modules/juce_core/text/juce_TextDiff.h new file mode 100644 index 000000000..d420c3d85 --- /dev/null +++ b/source/modules/juce_core/text/juce_TextDiff.h @@ -0,0 +1,80 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TEXTDIFF_H_INCLUDED +#define JUCE_TEXTDIFF_H_INCLUDED + + +/** + 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 JUCE_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 // JUCE_TEXTDIFF_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ChildProcess.cpp b/source/modules/juce_core/threads/juce_ChildProcess.cpp new file mode 100644 index 000000000..7edab2a5f --- /dev/null +++ b/source/modules/juce_core/threads/juce_ChildProcess.cpp @@ -0,0 +1,93 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_UNIT_TESTS + +class ChildProcessTests : public UnitTest +{ +public: + ChildProcessTests() : UnitTest ("ChildProcess") {} + + void runTest() + { + beginTest ("Child Processes"); + + #if JUCE_WINDOWS || JUCE_MAC || JUCE_LINUX + ChildProcess p; + + #if JUCE_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/source/modules/juce_core/threads/juce_ChildProcess.h b/source/modules/juce_core/threads/juce_ChildProcess.h new file mode 100644 index 000000000..574ac9fa6 --- /dev/null +++ b/source/modules/juce_core/threads/juce_ChildProcess.h @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHILDPROCESS_H_INCLUDED +#define JUCE_CHILDPROCESS_H_INCLUDED + + +//============================================================================== +/** + 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 JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess) +}; + + +#endif // JUCE_CHILDPROCESS_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_CriticalSection.h b/source/modules/juce_core/threads/juce_CriticalSection.h new file mode 100644 index 000000000..8677f4e71 --- /dev/null +++ b/source/modules/juce_core/threads/juce_CriticalSection.h @@ -0,0 +1,257 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CRITICALSECTION_H_INCLUDED +#define JUCE_CRITICALSECTION_H_INCLUDED + +#include "juce_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 JUCE_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 JUCE_WINDOWS + // To avoid including windows.h in the public JUCE 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 JUCE_64BIT + uint8 internal [44]; + #else + uint8 internal [24]; + #endif + #else + mutable pthread_mutex_t internal; + #endif + + JUCE_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 JUCE_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: + JUCE_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 // JUCE_CRITICALSECTION_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_DynamicLibrary.h b/source/modules/juce_core/threads/juce_DynamicLibrary.h new file mode 100644 index 000000000..df6625b54 --- /dev/null +++ b/source/modules/juce_core/threads/juce_DynamicLibrary.h @@ -0,0 +1,85 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_DYNAMICLIBRARY_H_INCLUDED +#define JUCE_DYNAMICLIBRARY_H_INCLUDED + +/** + 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 JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DynamicLibrary) +}; + + +#endif // JUCE_DYNAMICLIBRARY_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp b/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp new file mode 100644 index 000000000..3475b74d1 --- /dev/null +++ b/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp @@ -0,0 +1,36 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +HighResolutionTimer::HighResolutionTimer() { pimpl = new Pimpl (*this); } +HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } + +void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (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/source/modules/juce_core/threads/juce_HighResolutionTimer.h b/source/modules/juce_core/threads/juce_HighResolutionTimer.h new file mode 100644 index 000000000..aae8390f9 --- /dev/null +++ b/source/modules/juce_core/threads/juce_HighResolutionTimer.h @@ -0,0 +1,109 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_HIGHRESOLUTIONTIMER_H_INCLUDED +#define JUCE_HIGHRESOLUTIONTIMER_H_INCLUDED + +/** + 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 JUCE_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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) +}; + + +#endif // JUCE_HIGHRESOLUTIONTIMER_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_InterProcessLock.h b/source/modules/juce_core/threads/juce_InterProcessLock.h new file mode 100644 index 000000000..654c2e8a5 --- /dev/null +++ b/source/modules/juce_core/threads/juce_InterProcessLock.h @@ -0,0 +1,131 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_INTERPROCESSLOCK_H_INCLUDED +#define JUCE_INTERPROCESSLOCK_H_INCLUDED + +#include "../text/juce_String.h" +#include "../memory/juce_ScopedPointer.h" + + +//============================================================================== +/** + Acts as a critical section which processes can use to block each other. + + @see CriticalSection +*/ +class JUCE_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; + + JUCE_DECLARE_NON_COPYABLE (ScopedLockType) + }; + +private: + //============================================================================== + class Pimpl; + friend class ScopedPointer ; + ScopedPointer pimpl; + + CriticalSection lock; + String name; + + JUCE_DECLARE_NON_COPYABLE (InterProcessLock) +}; + + +#endif // JUCE_INTERPROCESSLOCK_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_Process.h b/source/modules/juce_core/threads/juce_Process.h new file mode 100644 index 000000000..56a1ad10e --- /dev/null +++ b/source/modules/juce_core/threads/juce_Process.h @@ -0,0 +1,155 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PROCESS_H_INCLUDED +#define JUCE_PROCESS_H_INCLUDED + +#include "../text/juce_String.h" + + +//============================================================================== +/** Represents the current executable's process. + + This contains methods for controlling the current application at the + process-level. + + @see Thread, JUCEApplication +*/ +class JUCE_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 JUCEApplication::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(); + + /** Hides the application (on an OS that supports this, e.g. OSX) */ + static void hide(); + + //============================================================================== + /** 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 JUCE_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 JUCE_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 JUCE application, this will be automatically set to the module + handle of the executable. + + If you've built a DLL and plan to use any JUCE 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* JUCE_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 JUCE_CALLTYPE setCurrentModuleInstanceHandle (void* newHandle) noexcept; + #endif + + #if JUCE_MAC || DOXYGEN + //============================================================================== + /** OSX ONLY - Shows or hides the OSX dock icon for this app. */ + static void setDockIconVisible (bool isVisible); + #endif + +private: + Process(); + JUCE_DECLARE_NON_COPYABLE (Process) +}; + + +#endif // JUCE_PROCESS_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ReadWriteLock.cpp b/source/modules/juce_core/threads/juce_ReadWriteLock.cpp new file mode 100644 index 000000000..7db6e921d --- /dev/null +++ b/source/modules/juce_core/threads/juce_ReadWriteLock.cpp @@ -0,0 +1,158 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +ReadWriteLock::ReadWriteLock() noexcept + : numWaitingWriters (0), + numWriters (0), + writerThreadId (0) +{ + readerThreads.ensureStorageAllocated (16); +} + +ReadWriteLock::~ReadWriteLock() noexcept +{ + jassert (readerThreads.size() == 0); + jassert (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.. + jassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); + + if (--numWriters == 0) + { + writerThreadId = 0; + waitEvent.signal(); + } +} diff --git a/source/modules/juce_core/threads/juce_ReadWriteLock.h b/source/modules/juce_core/threads/juce_ReadWriteLock.h new file mode 100644 index 000000000..c5be7c77a --- /dev/null +++ b/source/modules/juce_core/threads/juce_ReadWriteLock.h @@ -0,0 +1,159 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_READWRITELOCK_H_INCLUDED +#define JUCE_READWRITELOCK_H_INCLUDED + +#include "juce_CriticalSection.h" +#include "juce_SpinLock.h" +#include "juce_WaitableEvent.h" +#include "juce_Thread.h" +#include "../containers/juce_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 JUCE_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; + + JUCE_DECLARE_NON_COPYABLE (ReadWriteLock) +}; + + +#endif // JUCE_READWRITELOCK_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ScopedLock.h b/source/modules/juce_core/threads/juce_ScopedLock.h new file mode 100644 index 000000000..442551add --- /dev/null +++ b/source/modules/juce_core/threads/juce_ScopedLock.h @@ -0,0 +1,237 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDLOCK_H_INCLUDED +#define JUCE_SCOPEDLOCK_H_INCLUDED + + +//============================================================================== +/** + 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_; + + JUCE_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_; + + JUCE_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; + + JUCE_DECLARE_NON_COPYABLE (GenericScopedTryLock) +}; + + +#endif // JUCE_SCOPEDLOCK_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ScopedReadLock.h b/source/modules/juce_core/threads/juce_ScopedReadLock.h new file mode 100644 index 000000000..874195be4 --- /dev/null +++ b/source/modules/juce_core/threads/juce_ScopedReadLock.h @@ -0,0 +1,92 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDREADLOCK_H_INCLUDED +#define JUCE_SCOPEDREADLOCK_H_INCLUDED + +#include "juce_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 JUCE_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_; + + JUCE_DECLARE_NON_COPYABLE (ScopedReadLock) +}; + + +#endif // JUCE_SCOPEDREADLOCK_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ScopedWriteLock.h b/source/modules/juce_core/threads/juce_ScopedWriteLock.h new file mode 100644 index 000000000..f4a8e329a --- /dev/null +++ b/source/modules/juce_core/threads/juce_ScopedWriteLock.h @@ -0,0 +1,92 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDWRITELOCK_H_INCLUDED +#define JUCE_SCOPEDWRITELOCK_H_INCLUDED + +#include "juce_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 JUCE_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_; + + JUCE_DECLARE_NON_COPYABLE (ScopedWriteLock) +}; + + +#endif // JUCE_SCOPEDWRITELOCK_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_SpinLock.h b/source/modules/juce_core/threads/juce_SpinLock.h new file mode 100644 index 000000000..e7a450560 --- /dev/null +++ b/source/modules/juce_core/threads/juce_SpinLock.h @@ -0,0 +1,93 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SPINLOCK_H_INCLUDED +#define JUCE_SPINLOCK_H_INCLUDED + +#include "juce_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 JUCE_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 + { + jassert (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; + + JUCE_DECLARE_NON_COPYABLE (SpinLock) +}; + + +#endif // JUCE_SPINLOCK_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_Thread.cpp b/source/modules/juce_core/threads/juce_Thread.cpp new file mode 100644 index 000000000..20d25398d --- /dev/null +++ b/source/modules/juce_core/threads/juce_Thread.cpp @@ -0,0 +1,363 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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. + */ + jassert (! 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; + + JUCE_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; + + JUCE_TRY + { + if (threadName.isNotEmpty()) + setCurrentThreadName (threadName); + + if (startSuspensionEvent.wait (10000)) + { + jassert (getCurrentThreadId() == threadId); + + if (affinityMask != 0) + setCurrentThreadAffinityMask (affinityMask); + + run(); + } + } + JUCE_CATCH_ALL_ASSERT + + currentThreadHolder->value.releaseCurrentThreadStorage(); + closeThreadHandle(); +} + +// used to wrap the incoming call from the platform-specific code +void JUCE_API juce_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?? + jassert (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?? + jassert (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 JUCE_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) 0x1122334455667788ULL) == 0x8877665544332211LL); + + 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 ! JUCE_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/source/modules/juce_core/threads/juce_Thread.h b/source/modules/juce_core/threads/juce_Thread.h new file mode 100644 index 000000000..1f6cff304 --- /dev/null +++ b/source/modules/juce_core/threads/juce_Thread.h @@ -0,0 +1,292 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_THREAD_H_INCLUDED +#define JUCE_THREAD_H_INCLUDED + +#include "juce_WaitableEvent.h" +#include "juce_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 JUCE_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 JUCE_CALLTYPE sleep (int milliseconds); + + /** Yields the calling thread's current time-slot. */ + static void JUCE_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-Juce 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 JUCE_API juce_threadEntryPoint (void*); + #endif + + void launchThread(); + void closeThreadHandle(); + void killThread(); + void threadEntryPoint(); + static bool setThreadPriority (void*, int); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread) +}; + +#endif // JUCE_THREAD_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ThreadLocalValue.h b/source/modules/juce_core/threads/juce_ThreadLocalValue.h new file mode 100644 index 000000000..5cb13672e --- /dev/null +++ b/source/modules/juce_core/threads/juce_ThreadLocalValue.h @@ -0,0 +1,199 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_THREADLOCALVALUE_H_INCLUDED +#define JUCE_THREADLOCALVALUE_H_INCLUDED + +// (NB: on win32, native thread-locals aren't possible in a dynamically loaded DLL in XP). +#if ! ((JUCE_MSVC && (JUCE_64BIT || ! defined (JucePlugin_PluginCode))) \ + || (JUCE_MAC && JUCE_CLANG && defined (MAC_OS_X_VERSION_10_7) \ + && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) + #define JUCE_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 JUCE_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 JUCE_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 JUCE_MAC + static __thread Type object; + return object; + #elif JUCE_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 JUCE_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 JUCE_NO_COMPILER_THREAD_LOCAL + struct ObjectHolder + { + ObjectHolder (const Thread::ThreadID& tid) + : threadId (tid), next (nullptr), object() + {} + + Thread::ThreadID threadId; + ObjectHolder* next; + Type object; + + JUCE_DECLARE_NON_COPYABLE (ObjectHolder) + }; + + mutable Atomic first; + SpinLock lock; + #endif + + JUCE_DECLARE_NON_COPYABLE (ThreadLocalValue) +}; + + +#endif // JUCE_THREADLOCALVALUE_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_ThreadPool.cpp b/source/modules/juce_core/threads/juce_ThreadPool.cpp new file mode 100644 index 000000000..257572a54 --- /dev/null +++ b/source/modules/juce_core/threads/juce_ThreadPool.cpp @@ -0,0 +1,380 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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! + jassert (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() override + { + while (! threadShouldExit()) + { + if (! pool.runNextJob()) + wait (500); + } + } + +private: + ThreadPool& pool; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) +}; + +//============================================================================== +ThreadPool::ThreadPool (const int numThreads) +{ + jassert (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 = jmax (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) +{ + jassert (job != nullptr); + jassert (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; + + JUCE_TRY + { + result = job->runJob(); + } + JUCE_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/source/modules/juce_core/threads/juce_ThreadPool.h b/source/modules/juce_core/threads/juce_ThreadPool.h new file mode 100644 index 000000000..f91bb5492 --- /dev/null +++ b/source/modules/juce_core/threads/juce_ThreadPool.h @@ -0,0 +1,318 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_THREADPOOL_H_INCLUDED +#define JUCE_THREADPOOL_H_INCLUDED + +#include "juce_Thread.h" +#include "../text/juce_StringArray.h" +#include "../containers/juce_Array.h" +#include "../containers/juce_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 JUCE_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; + + JUCE_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 JUCE_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 JUCE_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); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPool) +}; + + +#endif // JUCE_THREADPOOL_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_TimeSliceThread.cpp b/source/modules/juce_core/threads/juce_TimeSliceThread.cpp new file mode 100644 index 000000000..f055a15e2 --- /dev/null +++ b/source/modules/juce_core/threads/juce_TimeSliceThread.cpp @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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) jmin ((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/source/modules/juce_core/threads/juce_TimeSliceThread.h b/source/modules/juce_core/threads/juce_TimeSliceThread.h new file mode 100644 index 000000000..b98fb3361 --- /dev/null +++ b/source/modules/juce_core/threads/juce_TimeSliceThread.h @@ -0,0 +1,152 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TIMESLICETHREAD_H_INCLUDED +#define JUCE_TIMESLICETHREAD_H_INCLUDED + +#include "juce_Thread.h" +#include "../containers/juce_Array.h" +#include "../time/juce_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 JUCE_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 JUCE_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() override; + #endif + + //============================================================================== +private: + CriticalSection callbackLock, listLock; + Array clients; + TimeSliceClient* clientBeingCalled; + + TimeSliceClient* getNextClient (int index) const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimeSliceThread) +}; + + +#endif // JUCE_TIMESLICETHREAD_H_INCLUDED diff --git a/source/modules/juce_core/threads/juce_WaitableEvent.h b/source/modules/juce_core/threads/juce_WaitableEvent.h new file mode 100644 index 000000000..f0f1ad105 --- /dev/null +++ b/source/modules/juce_core/threads/juce_WaitableEvent.h @@ -0,0 +1,121 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WAITABLEEVENT_H_INCLUDED +#define JUCE_WAITABLEEVENT_H_INCLUDED + +#include "../text/juce_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 JUCE_API WaitableEvent +{ +public: + //============================================================================== + /** Creates a WaitableEvent object. + + The object is initially in an unsignalled state. + + @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. + */ + explicit 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 JUCE_WINDOWS + void* internal; + #else + mutable pthread_cond_t condition; + mutable pthread_mutex_t mutex; + mutable bool triggered, manualReset; + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaitableEvent) +}; + + +#endif // JUCE_WAITABLEEVENT_H_INCLUDED diff --git a/source/modules/juce_core/time/juce_PerformanceCounter.cpp b/source/modules/juce_core/time/juce_PerformanceCounter.cpp new file mode 100644 index 000000000..64da598ae --- /dev/null +++ b/source/modules/juce_core/time/juce_PerformanceCounter.cpp @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +PerformanceCounter::PerformanceCounter (const String& name_, + const int runsPerPrintout, + const File& loggingFile) + : name (name_), + numRuns (0), + runsPerPrint (runsPerPrintout), + totalTime (0), + started (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/source/modules/juce_core/time/juce_PerformanceCounter.h b/source/modules/juce_core/time/juce_PerformanceCounter.h new file mode 100644 index 000000000..d4088a24c --- /dev/null +++ b/source/modules/juce_core/time/juce_PerformanceCounter.h @@ -0,0 +1,108 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PERFORMANCECOUNTER_H_INCLUDED +#define JUCE_PERFORMANCECOUNTER_H_INCLUDED + +#include "../files/juce_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 JUCE_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 // JUCE_PERFORMANCECOUNTER_H_INCLUDED diff --git a/source/modules/juce_core/time/juce_RelativeTime.cpp b/source/modules/juce_core/time/juce_RelativeTime.cpp new file mode 100644 index 000000000..8d4804911 --- /dev/null +++ b/source/modules/juce_core/time/juce_RelativeTime.cpp @@ -0,0 +1,138 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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/source/modules/juce_core/time/juce_RelativeTime.h b/source/modules/juce_core/time/juce_RelativeTime.h new file mode 100644 index 000000000..7116230bd --- /dev/null +++ b/source/modules/juce_core/time/juce_RelativeTime.h @@ -0,0 +1,181 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RELATIVETIME_H_INCLUDED +#define JUCE_RELATIVETIME_H_INCLUDED + +#include "../text/juce_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 JUCE_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 // JUCE_RELATIVETIME_H_INCLUDED diff --git a/source/modules/juce_core/time/juce_Time.cpp b/source/modules/juce_core/time/juce_Time.cpp new file mode 100644 index 000000000..b83b4a75c --- /dev/null +++ b/source/modules/juce_core/time/juce_Time.cpp @@ -0,0 +1,446 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +namespace TimeHelpers +{ + static struct tm millisToLocal (const int64 millis) noexcept + { + struct tm result; + const int64 seconds = millis / 1000; + + if (seconds < 86400LL || seconds >= 2145916800LL) + { + // 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 + 210866803200LL; + + const int days = (int) (jdm / 86400LL); + 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 % 86400LL); + 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 JUCE_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 JUCE_ANDROID + typedef CharPointer_UTF8 StringType; + #elif JUCE_WINDOWS + typedef CharPointer_UTF16 StringType; + #else + typedef CharPointer_UTF32 StringType; + #endif + + for (size_t bufferSize = 256; ; bufferSize += 256) + { + HeapBlock buffer (bufferSize); + + #if JUCE_ANDROID + const size_t numChars = strftime (buffer, bufferSize - 1, format.toUTF8(), tm); + #elif JUCE_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 +{ + jassert (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) * 86400LL - 210866803200LL; + + 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 JUCE_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 JUCE_CALLTYPE Time::getCurrentTime() noexcept +{ + return Time (currentTimeMillis()); +} + +//============================================================================== +uint32 juce_millisecondsSinceStartup() noexcept; + +uint32 Time::getMillisecondCounter() noexcept +{ + const uint32 now = juce_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 (jmin (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 JUCE_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/source/modules/juce_core/time/juce_Time.h b/source/modules/juce_core/time/juce_Time.h new file mode 100644 index 000000000..5fa7cee47 --- /dev/null +++ b/source/modules/juce_core/time/juce_Time.h @@ -0,0 +1,406 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TIME_H_INCLUDED +#define JUCE_TIME_H_INCLUDED + +#include "juce_RelativeTime.h" + + +//============================================================================== +/** + Holds an absolute date and time. + + Internally, the time is stored at millisecond precision. + + @see RelativeTime +*/ +class JUCE_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 JUCE_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. */ +JUCE_API Time operator+ (Time time, RelativeTime delta); +/** Adds a RelativeTime to a Time. */ +JUCE_API Time operator+ (RelativeTime delta, Time time); + +/** Subtracts a RelativeTime from a Time. */ +JUCE_API Time operator- (Time time, RelativeTime delta); +/** Returns the relative time difference between two times. */ +JUCE_API const RelativeTime operator- (Time time1, Time time2); + +/** Compares two Time objects. */ +JUCE_API bool operator== (Time time1, Time time2); +/** Compares two Time objects. */ +JUCE_API bool operator!= (Time time1, Time time2); +/** Compares two Time objects. */ +JUCE_API bool operator< (Time time1, Time time2); +/** Compares two Time objects. */ +JUCE_API bool operator<= (Time time1, Time time2); +/** Compares two Time objects. */ +JUCE_API bool operator> (Time time1, Time time2); +/** Compares two Time objects. */ +JUCE_API bool operator>= (Time time1, Time time2); + + +#endif // JUCE_TIME_H_INCLUDED diff --git a/source/modules/juce_core/unit_tests/juce_UnitTest.cpp b/source/modules/juce_core/unit_tests/juce_UnitTest.cpp new file mode 100644 index 000000000..9fd6a8e38 --- /dev/null +++ b/source/modules/juce_core/unit_tests/juce_UnitTest.cpp @@ -0,0 +1,237 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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_) +{ + jassert (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(); + jassert (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(); + jassert (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/source/modules/juce_core/unit_tests/juce_UnitTest.h b/source/modules/juce_core/unit_tests/juce_UnitTest.h new file mode 100644 index 000000000..522c8f9b8 --- /dev/null +++ b/source/modules/juce_core/unit_tests/juce_UnitTest.h @@ -0,0 +1,290 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_UNITTEST_H_INCLUDED +#define JUCE_UNITTEST_H_INCLUDED + +#include "../text/juce_StringArray.h" +#include "../containers/juce_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 JUCE_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; + + JUCE_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 JUCE_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); + + JUCE_DECLARE_NON_COPYABLE (UnitTestRunner) +}; + + +#endif // JUCE_UNITTEST_H_INCLUDED diff --git a/source/modules/juce_core/xml/juce_XmlDocument.cpp b/source/modules/juce_core/xml/juce_XmlDocument.cpp new file mode 100644 index 000000000..0c0a422da --- /dev/null +++ b/source/modules/juce_core/xml/juce_XmlDocument.cpp @@ -0,0 +1,859 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +XmlDocument::XmlDocument (const String& documentText) + : originalText (documentText), + input (nullptr), + outOfData (false), + errorOccurred (false), + needToLoadDTD (false), + ignoreEmptyTextElements (true) +{ +} + +XmlDocument::XmlDocument (const File& file) + : input (nullptr), + outOfData (false), + errorOccurred (false), + needToLoadDTD (false), + 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 juce_wchar c) noexcept + { + return CharacterFunctions::isLetterOrDigit (c) + || c == '_' || c == '-' || c == ':' || c == '.'; + } + + static bool isIdentifierChar (const juce_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; +} + +juce_wchar XmlDocument::readNextChar() noexcept +{ + const juce_wchar c = input.getAndAdvance(); + + if (c == 0) + { + outOfData = true; + --input; + } + + return c; +} + +int XmlDocument::findNextTokenLength() noexcept +{ + int len = 0; + juce_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 JUCE_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 juce 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. + */ + jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-")); + #endif + + input += headerEnd + 2; + } + + skipNextWhiteSpace(); + + const int docTypeIndex = input.indexOf (CharPointer_UTF8 (" 0) + { + const juce_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 (;;) + { + juce_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 juce_wchar quote = readNextChar(); + + while (! outOfData) + { + const juce_wchar c = readNextChar(); + + if (c == quote) + break; + + --input; + + if (c == '&') + { + readEntity (result); + } + else + { + const String::CharPointerType start (input); + size_t numChars = 0; + + for (;;) + { + const juce_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 ((juce_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 juce_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 juce_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 ((juce_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 juce_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 juce_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 << (juce_wchar) charCode; + } + else + { + const String::CharPointerType entityNameStart (input); + const int closingSemiColon = input.indexOf ((juce_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 juce_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/source/modules/juce_core/xml/juce_XmlDocument.h b/source/modules/juce_core/xml/juce_XmlDocument.h new file mode 100644 index 000000000..48a6c3f5f --- /dev/null +++ b/source/modules/juce_core/xml/juce_XmlDocument.h @@ -0,0 +1,186 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_XMLDOCUMENT_H_INCLUDED +#define JUCE_XMLDOCUMENT_H_INCLUDED + +#include "juce_XmlElement.h" +#include "../text/juce_StringArray.h" +#include "../files/juce_File.h" +#include "../memory/juce_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 JUCE_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(); + juce_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); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) +}; + + +#endif // JUCE_XMLDOCUMENT_H_INCLUDED diff --git a/source/modules/juce_core/xml/juce_XmlElement.cpp b/source/modules/juce_core/xml/juce_XmlElement.cpp new file mode 100644 index 000000000..7b34f59f3 --- /dev/null +++ b/source/modules/juce_core/xml/juce_XmlElement.cpp @@ -0,0 +1,829 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +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 JUCE_DEBUG + // this checks whether the attribute name string contains any illegal characters.. + for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t) + jassert (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! + jassert (tag.containsNonWhitespaceChars()) + + // The tag can't contain spaces or other characters that would create invalid XML! + jassert (! 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 JUCE_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 +{ + jassert (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) +{ + jassert (firstChildElement.get() == nullptr); + firstChildElement.addCopyOfList (other.firstChildElement); + + jassert (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 juce_wchar character) noexcept + { + if ((character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9')) + return true; + + const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; + + do + { + if (((juce_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. + jassert ((! 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)) + { + juce_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 juce_xmltextContentAttributeName ("text"); + +const String& XmlElement::getText() const noexcept +{ + jassert (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 (juce_xmltextContentAttributeName); +} + +void XmlElement::setText (const String& newText) +{ + if (isTextElement()) + setAttribute (juce_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 (juce_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/source/modules/juce_core/xml/juce_XmlElement.h b/source/modules/juce_core/xml/juce_XmlElement.h new file mode 100644 index 000000000..89c97c178 --- /dev/null +++ b/source/modules/juce_core/xml/juce_XmlElement.h @@ -0,0 +1,739 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_XMLELEMENT_H_INCLUDED +#define JUCE_XMLELEMENT_H_INCLUDED + +#include "../text/juce_String.h" +#include "../streams/juce_OutputStream.h" +#include "../files/juce_File.h" +#include "../containers/juce_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 (juce::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 (juce::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 JUCE_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 JUCE_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; + + JUCE_LEAK_DETECTOR (XmlElement) +}; + + +#endif // JUCE_XMLELEMENT_H_INCLUDED diff --git a/source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp b/source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp new file mode 100644 index 000000000..feea3f7f0 --- /dev/null +++ b/source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp @@ -0,0 +1,213 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.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! + jassert (! 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; + } + + JUCE_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)) +{ + jassert (out != nullptr); +} + +GZIPCompressorOutputStream::~GZIPCompressorOutputStream() +{ + flush(); +} + +void GZIPCompressorOutputStream::flush() +{ + helper->finish (*destStream); + destStream->flush(); +} + +bool GZIPCompressorOutputStream::write (const void* destBuffer, size_t howMany) +{ + jassert (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 JUCE_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/source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h b/source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h new file mode 100644 index 000000000..9a2b93681 --- /dev/null +++ b/source/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_GZIPCOMPRESSOROUTPUTSTREAM_H_INCLUDED +#define JUCE_GZIPCOMPRESSOROUTPUTSTREAM_H_INCLUDED + +#include "../streams/juce_OutputStream.h" +#include "../memory/juce_OptionalScopedPointer.h" +#include "../memory/juce_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 JUCE_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() override; + bool setPosition (int64) override; + bool write (const void*, size_t) override; + + /** 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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPCompressorOutputStream) +}; + +#endif // JUCE_GZIPCOMPRESSOROUTPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp b/source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp new file mode 100644 index 000000000..d67c6a557 --- /dev/null +++ b/source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp @@ -0,0 +1,290 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4309 4305 4365) +#endif + +namespace zlibNamespace +{ + #if JUCE_INCLUDE_ZLIB_CODE + #if JUCE_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 JUCE_CLANG + #pragma clang diagnostic pop + #endif + #else + #include JUCE_ZLIB_INCLUDE_PATH + #endif +} + +#if JUCE_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; + + JUCE_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) +{ + jassert (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 juce_crc32 (unsigned long, const unsigned char*, unsigned); +unsigned long juce_crc32 (unsigned long crc, const unsigned char* buf, unsigned len) +{ + return zlibNamespace::crc32 (crc, buf, len); +} diff --git a/source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h b/source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h new file mode 100644 index 000000000..bc859d488 --- /dev/null +++ b/source/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h @@ -0,0 +1,101 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_GZIPDECOMPRESSORINPUTSTREAM_H_INCLUDED +#define JUCE_GZIPDECOMPRESSORINPUTSTREAM_H_INCLUDED + +#include "../streams/juce_InputStream.h" +#include "../memory/juce_OptionalScopedPointer.h" +#include "../memory/juce_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 JUCE_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() override; + bool setPosition (int64 pos) override; + int64 getTotalLength() override; + bool isExhausted() override; + int read (void* destBuffer, int maxBytesToRead) override; + +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; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPDecompressorInputStream) +}; + +#endif // JUCE_GZIPDECOMPRESSORINPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_core/zip/juce_ZipFile.cpp b/source/modules/juce_core/zip/juce_ZipFile.cpp new file mode 100644 index 000000000..d3e83894c --- /dev/null +++ b/source/modules/juce_core/zip/juce_ZipFile.cpp @@ -0,0 +1,598 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.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 = jmax ((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 JUCE_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 JUCE_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) jmin ((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 = jlimit ((int64) 0, (int64) zipEntryHolder.compressedSize, newPos); + return true; + } + +private: + ZipFile& file; + ZipEntryHolder zipEntryHolder; + int64 pos; + int headerSize; + InputStream* inputStream; + ScopedPointer streamToDelete; + + JUCE_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 JUCE_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. + */ + jassert (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 JUCE_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 juce_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), + checksum (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 = juce_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 + } + + JUCE_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/source/modules/juce_core/zip/juce_ZipFile.h b/source/modules/juce_core/zip/juce_ZipFile.h new file mode 100644 index 000000000..1b8f120e4 --- /dev/null +++ b/source/modules/juce_core/zip/juce_ZipFile.h @@ -0,0 +1,249 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE 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 juce_core module! + All other JUCE 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.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ZIPFILE_H_INCLUDED +#define JUCE_ZIPFILE_H_INCLUDED + +#include "../files/juce_File.h" +#include "../streams/juce_InputSource.h" +#include "../threads/juce_CriticalSection.h" +#include "../containers/juce_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 JUCE_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; + + JUCE_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 JUCE_DEBUG + struct OpenStreamCounter + { + OpenStreamCounter() : numOpenStreams (0) {} + ~OpenStreamCounter(); + + int numOpenStreams; + }; + + OpenStreamCounter streamCounter; + #endif + + void init(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipFile) +}; + +#endif // JUCE_ZIPFILE_H_INCLUDED diff --git a/source/modules/juce_core/zip/zlib/README b/source/modules/juce_core/zip/zlib/README new file mode 100644 index 000000000..758cc5002 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/adler32.c b/source/modules/juce_core/zip/zlib/adler32.c new file mode 100644 index 000000000..bf5cbd200 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/compress.c b/source/modules/juce_core/zip/zlib/compress.c new file mode 100644 index 000000000..05c49c552 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/crc32.c b/source/modules/juce_core/zip/zlib/crc32.c new file mode 100644 index 000000000..049276db5 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/crc32.h b/source/modules/juce_core/zip/zlib/crc32.h new file mode 100644 index 000000000..5de49bc97 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/deflate.c b/source/modules/juce_core/zip/zlib/deflate.c new file mode 100644 index 000000000..4b8bda58a --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/deflate.h b/source/modules/juce_core/zip/zlib/deflate.h new file mode 100644 index 000000000..a3bae5f1e --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/infback.c b/source/modules/juce_core/zip/zlib/infback.c new file mode 100644 index 000000000..c259d01fc --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inffast.c b/source/modules/juce_core/zip/zlib/inffast.c new file mode 100644 index 000000000..6ac383d4e --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inffast.h b/source/modules/juce_core/zip/zlib/inffast.h new file mode 100644 index 000000000..614fa7877 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inffixed.h b/source/modules/juce_core/zip/zlib/inffixed.h new file mode 100644 index 000000000..423d5c5b5 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inflate.c b/source/modules/juce_core/zip/zlib/inflate.c new file mode 100644 index 000000000..f70c52ba9 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inflate.h b/source/modules/juce_core/zip/zlib/inflate.h new file mode 100644 index 000000000..31b1279c5 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inftrees.c b/source/modules/juce_core/zip/zlib/inftrees.c new file mode 100644 index 000000000..dfc0aa73e --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/inftrees.h b/source/modules/juce_core/zip/zlib/inftrees.h new file mode 100644 index 000000000..ea64af090 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/trees.c b/source/modules/juce_core/zip/zlib/trees.c new file mode 100644 index 000000000..91ed76601 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/trees.h b/source/modules/juce_core/zip/zlib/trees.h new file mode 100644 index 000000000..5ac45a723 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/uncompr.c b/source/modules/juce_core/zip/zlib/uncompr.c new file mode 100644 index 000000000..839602f4b --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/zconf.h b/source/modules/juce_core/zip/zlib/zconf.h new file mode 100644 index 000000000..f1e9e870b --- /dev/null +++ b/source/modules/juce_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 Juce.. +#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/source/modules/juce_core/zip/zlib/zconf.in.h b/source/modules/juce_core/zip/zlib/zconf.in.h new file mode 100644 index 000000000..018173ab6 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/zlib.h b/source/modules/juce_core/zip/zlib/zlib.h new file mode 100644 index 000000000..bdf8af7d0 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/zutil.c b/source/modules/juce_core/zip/zlib/zutil.c new file mode 100644 index 000000000..630305ca2 --- /dev/null +++ b/source/modules/juce_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/source/modules/juce_core/zip/zlib/zutil.h b/source/modules/juce_core/zip/zlib/zutil.h new file mode 100644 index 000000000..393985870 --- /dev/null +++ b/source/modules/juce_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 */