| @@ -199,3 +199,6 @@ data/macos/*.*/ | |||
| data/macos/Carla/ | |||
| data/windows/python/ | |||
| source/modules/juce_audio_graph/test | |||
| source/modules/juce_audio_graph/test.d | |||
| @@ -569,7 +569,7 @@ public: | |||
| { | |||
| float* const d = channels [channel] + startSample; | |||
| if (gain == 0.0f) | |||
| if (carla_isZero (gain)) | |||
| carla_zeroFloats (d, numSamples); | |||
| else | |||
| carla_multiply (d, gain, numSamples); | |||
| @@ -0,0 +1,373 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #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<MyObject> instead). | |||
| LinkedListPointer<MyObject> nextListItem; | |||
| }; | |||
| LinkedListPointer<MyObject> myList; | |||
| myList.append (new MyObject()); | |||
| myList.append (new MyObject()); | |||
| int numItems = myList.size(); // returns 2 | |||
| MyObject* lastInList = myList.getLast(); | |||
| @endcode | |||
| */ | |||
| template <class ObjectType> | |||
| 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 | |||
| @@ -0,0 +1,856 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||
| #define JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Holds a list of objects derived from ReferenceCountedObject, or which implement basic | |||
| reference-count handling methods. | |||
| 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 | |||
| or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||
| class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||
| decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||
| should behave. | |||
| 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 ObjectClass> | |||
| class ReferenceCountedArray | |||
| { | |||
| public: | |||
| typedef ReferenceCountedObjectPtr<ObjectClass> ObjectClassPtr; | |||
| //============================================================================== | |||
| /** Creates an empty array. | |||
| @see ReferenceCountedObject, Array, OwnedArray | |||
| */ | |||
| ReferenceCountedArray() noexcept | |||
| : numUsed (0) | |||
| { | |||
| } | |||
| /** Creates a copy of another array */ | |||
| ReferenceCountedArray (const ReferenceCountedArray& other) noexcept | |||
| { | |||
| numUsed = other.size(); | |||
| data.setAllocatedSize (numUsed); | |||
| memcpy (data.elements, other.getRawDataPointer(), (size_t) numUsed * sizeof (ObjectClass*)); | |||
| for (int i = numUsed; --i >= 0;) | |||
| if (ObjectClass* o = data.elements[i]) | |||
| o->incReferenceCount(); | |||
| } | |||
| /** Creates a copy of another array */ | |||
| template <class OtherObjectClass> | |||
| ReferenceCountedArray (const ReferenceCountedArray<OtherObjectClass>& other) noexcept | |||
| { | |||
| 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 <class OtherObjectClass> | |||
| ReferenceCountedArray<ObjectClass>& operator= (const ReferenceCountedArray<OtherObjectClass>& other) noexcept | |||
| { | |||
| ReferenceCountedArray<ObjectClass> 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() | |||
| { | |||
| releaseAllObjects(); | |||
| } | |||
| //============================================================================== | |||
| /** Removes all objects from the array. | |||
| Any objects in the array that whose reference counts drop to zero will be deleted. | |||
| */ | |||
| void clear() | |||
| { | |||
| releaseAllObjects(); | |||
| data.setAllocatedSize (0); | |||
| } | |||
| /** Removes all objects from the array without freeing the array's allocated storage. | |||
| Any objects in the array that whose reference counts drop to zero will be deleted. | |||
| @see clear | |||
| */ | |||
| void clearQuick() | |||
| { | |||
| releaseAllObjects(); | |||
| } | |||
| /** Returns the current number of objects in the array. */ | |||
| inline int size() const noexcept | |||
| { | |||
| return numUsed; | |||
| } | |||
| /** Returns true if the array is empty, false otherwise. */ | |||
| inline bool isEmpty() const noexcept | |||
| { | |||
| return size() == 0; | |||
| } | |||
| /** 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 | |||
| { | |||
| if (isPositiveAndBelow (index, numUsed)) | |||
| { | |||
| jassert (data.elements != nullptr); | |||
| return data.elements [index]; | |||
| } | |||
| return ObjectClassPtr(); | |||
| } | |||
| /** 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 | |||
| { | |||
| 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 ObjectClassPtr getFirst() const noexcept | |||
| { | |||
| if (numUsed > 0) | |||
| { | |||
| jassert (data.elements != nullptr); | |||
| return data.elements [0]; | |||
| } | |||
| return ObjectClassPtr(); | |||
| } | |||
| /** 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 | |||
| { | |||
| if (numUsed > 0) | |||
| { | |||
| jassert (data.elements != nullptr); | |||
| return data.elements [numUsed - 1]; | |||
| } | |||
| return ObjectClassPtr(); | |||
| } | |||
| /** 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 | |||
| { | |||
| ObjectClass** e = data.elements.getData(); | |||
| ObjectClass** const endPointer = e + numUsed; | |||
| while (e != endPointer) | |||
| { | |||
| if (objectToLookFor == *e) | |||
| return static_cast<int> (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 | |||
| { | |||
| 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 | |||
| { | |||
| 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) | |||
| return add (newObject); | |||
| 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; | |||
| } | |||
| /** 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 | |||
| @returns true if the object has been added, false otherwise | |||
| */ | |||
| bool addIfNotAlreadyThere (ObjectClass* const newObject) noexcept | |||
| { | |||
| if (contains (newObject)) | |||
| return false; | |||
| add (newObject); | |||
| return true; | |||
| } | |||
| /** 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) | |||
| { | |||
| if (newObject != nullptr) | |||
| newObject->incReferenceCount(); | |||
| if (indexToChange < numUsed) | |||
| { | |||
| if (ObjectClass* o = data.elements [indexToChange]) | |||
| releaseObject (o); | |||
| 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<ObjectClass>& arrayToAddFrom, | |||
| int startIndex = 0, | |||
| int numElementsToAdd = -1) noexcept | |||
| { | |||
| 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 <class ElementComparator> | |||
| int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept | |||
| { | |||
| 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 <class ElementComparator> | |||
| void addOrReplaceSorted (ElementComparator& comparator, | |||
| ObjectClass* newObject) noexcept | |||
| { | |||
| 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 <class ElementComparator> | |||
| int indexOfSorted (ElementComparator& comparator, | |||
| const ObjectClass* const objectToLookFor) const noexcept | |||
| { | |||
| ignoreUnused (comparator); | |||
| 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) | |||
| { | |||
| if (isPositiveAndBelow (indexToRemove, numUsed)) | |||
| { | |||
| ObjectClass** const e = data.elements + indexToRemove; | |||
| if (ObjectClass* o = *e) | |||
| releaseObject (o); | |||
| --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; | |||
| if (isPositiveAndBelow (indexToRemove, numUsed)) | |||
| { | |||
| ObjectClass** const e = data.elements + indexToRemove; | |||
| if (ObjectClass* o = *e) | |||
| { | |||
| removedItem = o; | |||
| releaseObject (o); | |||
| } | |||
| --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) | |||
| { | |||
| 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 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]) | |||
| { | |||
| releaseObject (o); | |||
| 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) | |||
| { | |||
| 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 | |||
| { | |||
| 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) | |||
| { | |||
| 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 <class OtherArrayType> | |||
| void swapWith (OtherArrayType& otherArray) noexcept | |||
| { | |||
| 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 | |||
| { | |||
| 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<ObjectClass>& 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 <class ElementComparator> | |||
| void sort (ElementComparator& comparator, | |||
| const bool retainOrderOfEquivalentItems = false) const noexcept | |||
| { | |||
| ignoreUnused (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); | |||
| } | |||
| //============================================================================== | |||
| /** 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 | |||
| { | |||
| 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) | |||
| { | |||
| data.ensureAllocatedSize (minNumElements); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| ArrayAllocationBase <ObjectClass*> data; | |||
| int numUsed; | |||
| void releaseAllObjects() | |||
| { | |||
| while (numUsed > 0) | |||
| if (ObjectClass* o = data.elements [--numUsed]) | |||
| releaseObject (o); | |||
| jassert (numUsed == 0); | |||
| } | |||
| static void releaseObject (ObjectClass* o) | |||
| { | |||
| if (o->decReferenceCountWithoutDeleting()) | |||
| delete o; | |||
| } | |||
| }; | |||
| #endif // JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||
| @@ -0,0 +1,469 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_SORTEDSET_H_INCLUDED | |||
| #define JUCE_SORTEDSET_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 ElementType> | |||
| 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<ElementType>& 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<ElementType>& 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 true if the set is empty, false otherwise. */ | |||
| inline bool isEmpty() const noexcept | |||
| { | |||
| return size() == 0; | |||
| } | |||
| /** 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 | |||
| { | |||
| 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; | |||
| 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 | |||
| { | |||
| 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; | |||
| } | |||
| 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 | |||
| { | |||
| 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 <class OtherSetType> | |||
| void addSet (const OtherSetType& setToAddFrom, | |||
| int startIndex = 0, | |||
| int numElementsToAdd = -1) noexcept | |||
| { | |||
| 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.removeAndReturn (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 | |||
| { | |||
| 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 <class OtherSetType> | |||
| void removeValuesIn (const OtherSetType& otherSet) noexcept | |||
| { | |||
| 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 <class OtherSetType> | |||
| void removeValuesNotIn (const OtherSetType& otherSet) noexcept | |||
| { | |||
| 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 <class OtherSetType> | |||
| 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); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| Array<ElementType> data; | |||
| }; | |||
| #if JUCE_MSVC | |||
| #pragma warning (pop) | |||
| #endif | |||
| #endif // JUCE_SORTEDSET_H_INCLUDED | |||
| @@ -0,0 +1,987 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_FILE_H_INCLUDED | |||
| #define JUCE_FILE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 getFullPathName() will return | |||
| an empty string. | |||
| 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 | |||
| //============================================================================== | |||
| #if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
| /** This static constant is used for referring to an 'invalid' file. | |||
| Bear in mind that you should avoid this kind of static variable, and always prefer | |||
| to use File() or {} if you need a default-constructed File object. | |||
| */ | |||
| static const File nonexistent; | |||
| #endif | |||
| //============================================================================== | |||
| /** 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 (StringRef 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 (StringRef 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 (StringRef 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 (StringRef 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; | |||
| /** Changes the execute-permissions of a file. | |||
| @param shouldBeExecutable whether to add or remove execute-permission | |||
| @returns true if it manages to change the file's permissions. | |||
| */ | |||
| bool setExecutePermission (bool shouldBeExecutable) 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; | |||
| /** Returns a unique identifier for the file, if one is available. | |||
| Depending on the OS and file-system, this may be a unix inode number or | |||
| a win32 file identifier, or 0 if it fails to find one. The number will | |||
| be unique on the filesystem, but not globally. | |||
| */ | |||
| uint64 getFileIdentifier() 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. | |||
| If the parent directories of the File do not exist then this method will | |||
| recursively create the parent directories. | |||
| @returns a result to indicate whether the file was created successfully, | |||
| or an error message if it failed. | |||
| @see createDirectory | |||
| */ | |||
| Result create() const; | |||
| /** Creates a new directory for this filename. | |||
| This will try to create the file as a directory, and will 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. | |||
| Also note that on some OSes (e.g. Windows), moving files between different | |||
| volumes may not be possible. | |||
| @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; | |||
| /** Replaces a file. | |||
| Replace the file in the given location, assuming the replaced files identity. | |||
| Depending on the file system this will preserve file attributes such as | |||
| creation date, short file name, etc. | |||
| If replacement succeeds the original file is deleted. | |||
| @returns true if the operation succeeds | |||
| */ | |||
| bool replaceFileIn (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<File>& 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 (size_t 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<File>& 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()) 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 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, | |||
| /** 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, | |||
| /** A place to put documents which are shared by all users of the machine. | |||
| On Windows this may be somewhere like "C:\Users\Public\Documents", on OSX it | |||
| will be something like "/Users/Shared". Other OSes may have no such concept | |||
| though, so be careful. | |||
| */ | |||
| commonDocumentsDirectory, | |||
| /** 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, | |||
| #if JUCE_WINDOWS | |||
| /** On a Windows machine, returns the location of the Windows/System32 folder. */ | |||
| windowsSystemDirectory, | |||
| #endif | |||
| /** 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 | |||
| }; | |||
| /** 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 (StringRef 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 (StringRef 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); | |||
| //============================================================================== | |||
| /** Tries to create a symbolic link and returns a boolean to indicate success */ | |||
| bool createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const; | |||
| /** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ | |||
| bool isSymbolicLink() const; | |||
| /** If this file is a link or alias, this returns the file that it points to. | |||
| If the file isn't actually link, it'll just return itself. | |||
| */ | |||
| File getLinkedTarget() const; | |||
| #if JUCE_WINDOWS | |||
| /** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ | |||
| bool createShortcut (const String& description, const File& linkFileToCreate) const; | |||
| /** Windows ONLY - Returns true if this is a win32 .LNK file. */ | |||
| bool isShortcut() const; | |||
| #endif | |||
| //============================================================================== | |||
| #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 | |||
| //============================================================================== | |||
| struct NaturalFileComparator | |||
| { | |||
| NaturalFileComparator (bool shouldPutFoldersFirst) noexcept : foldersFirst (shouldPutFoldersFirst) {} | |||
| int compareElements (const File& firstFile, const File& secondFile) const | |||
| { | |||
| if (foldersFirst && (firstFile.isDirectory() != secondFile.isDirectory())) | |||
| return firstFile.isDirectory() ? -1 : 1; | |||
| #if NAMES_ARE_CASE_SENSITIVE | |||
| return firstFile.getFullPathName().compareNatural (secondFile.getFullPathName(), true); | |||
| #else | |||
| return firstFile.getFullPathName().compareNatural (secondFile.getFullPathName(), false); | |||
| #endif | |||
| } | |||
| bool foldersFirst; | |||
| }; | |||
| 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 replaceInternal (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; | |||
| bool setFileExecutableInternal (bool) const; | |||
| }; | |||
| #endif // JUCE_FILE_H_INCLUDED | |||
| @@ -0,0 +1,119 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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()); | |||
| } | |||
| 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()); | |||
| if (temporaryFile.exists()) | |||
| { | |||
| // Have a few attempts at overwriting the file before giving up.. | |||
| for (int i = 5; --i >= 0;) | |||
| { | |||
| if (temporaryFile.replaceFileIn (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; | |||
| } | |||
| @@ -0,0 +1,171 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_TEMPORARYFILE_H_INCLUDED | |||
| #define JUCE_TEMPORARYFILE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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<FileOutputStream> 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(), | |||
| 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 | |||
| @@ -18,13 +18,6 @@ | |||
| #include "juce_audio_graph.h" | |||
| // #include "AppConfig.h" | |||
| // #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| // #include "juce_audio_processors.h" | |||
| // #include <juce_gui_extra/juce_gui_extra.h> | |||
| #include <locale> | |||
| #include <cctype> | |||
| #include <cstdarg> | |||
| @@ -43,33 +36,13 @@ namespace juce2 | |||
| // #include "streams/juce_InputStream.cpp" | |||
| // #include "streams/juce_OutputStream.cpp" | |||
| // static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||
| // const PluginDescription& desc) | |||
| // { | |||
| // for (int i = list.size(); --i >= 0;) | |||
| // if (list.getUnchecked(i)->isDuplicateOf (desc)) | |||
| // return true; | |||
| // | |||
| // return false; | |||
| // } | |||
| #include "midi/juce_MidiBuffer.cpp" | |||
| #include "midi/juce_MidiMessage.cpp" | |||
| // #include "format/juce_AudioPluginFormat.cpp" | |||
| // #include "format/juce_AudioPluginFormatManager.cpp" | |||
| #include "processors/juce_AudioProcessor.cpp" | |||
| #include "processors/juce_AudioProcessorGraph.cpp" | |||
| // #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
| // #include "processors/juce_PluginDescription.cpp" | |||
| // #include "format_types/juce_LADSPAPluginFormat.cpp" | |||
| // #include "format_types/juce_VSTPluginFormat.cpp" | |||
| // #include "format_types/juce_VST3PluginFormat.cpp" | |||
| // #include "format_types/juce_AudioUnitPluginFormat.mm" | |||
| // #include "scanning/juce_KnownPluginList.cpp" | |||
| // #include "scanning/juce_PluginDirectoryScanner.cpp" | |||
| // #include "scanning/juce_PluginListComponent.cpp" | |||
| // #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
| // #include "utilities/juce_AudioProcessorParameters.cpp" | |||
| #include "xml/juce_XmlElement.cpp" | |||
| #include "xml/juce_XmlDocument.cpp" | |||
| } | |||
| @@ -109,11 +109,18 @@ | |||
| namespace juce2 | |||
| { | |||
| class DynamicObject; | |||
| class File; | |||
| class FileInputStream; | |||
| class FileOutputStream; | |||
| class InputSource; | |||
| class InputStream; | |||
| class MidiMessage; | |||
| class MemoryBlock; | |||
| class OutputStream; | |||
| class Result; | |||
| class StringRef; | |||
| class XmlElement; | |||
| #include "memory/juce_Memory.h" | |||
| #include "maths/juce_MathsFunctions.h" | |||
| @@ -123,47 +130,51 @@ class StringRef; | |||
| #include "text/juce_CharPointer_UTF8.h" | |||
| #include "text/juce_String.h" | |||
| #include "text/juce_StringRef.h" | |||
| #include "memory/juce_HeapBlock.h" | |||
| #include "containers/juce_ElementComparator.h" | |||
| #include "containers/juce_ArrayAllocationBase.h" | |||
| #include "containers/juce_Array.h" | |||
| #include "text/juce_StringArray.h" | |||
| #include "text/juce_StringRef.h" | |||
| #include "memory/juce_MemoryBlock.h" | |||
| #include "memory/juce_ReferenceCountedObject.h" | |||
| #include "text/juce_Identifier.h" | |||
| #include "text/juce_NewLine.h" | |||
| #include "containers/juce_ElementComparator.h" | |||
| #include "containers/juce_ArrayAllocationBase.h" | |||
| #include "containers/juce_Array.h" | |||
| #include "containers/juce_LinkedListPointer.h" | |||
| #include "containers/juce_OwnedArray.h" | |||
| #include "containers/juce_ReferenceCountedArray.h" | |||
| #include "containers/juce_SortedSet.h" | |||
| // #include "containers/juce_Variant.h" | |||
| // #include "containers/juce_NamedValueSet.h" | |||
| #include "containers/juce_Variant.h" | |||
| #include "containers/juce_NamedValueSet.h" | |||
| #include "streams/juce_InputSource.h" | |||
| #include "streams/juce_InputStream.h" | |||
| #include "streams/juce_OutputStream.h" | |||
| #include "streams/juce_MemoryOutputStream.h" | |||
| #include "misc/juce_Result.h" | |||
| #include "text/juce_StringPool.h" | |||
| #include "files/juce_File.h" | |||
| #include "streams/juce_FileInputStream.h" | |||
| #include "streams/juce_FileInputSource.h" | |||
| #include "buffers/juce_AudioSampleBuffer.h" | |||
| #include "midi/juce_MidiBuffer.h" | |||
| #include "midi/juce_MidiMessage.h" | |||
| class AudioProcessor; | |||
| #include "processors/juce_AudioPlayHead.h" | |||
| // #include "processors/juce_AudioProcessorListener.h" | |||
| // #include "processors/juce_AudioProcessorParameter.h" | |||
| #include "processors/juce_AudioProcessor.h" | |||
| // #include "processors/juce_PluginDescription.h" | |||
| // #include "processors/juce_AudioPluginInstance.h" | |||
| #include "processors/juce_AudioProcessorGraph.h" | |||
| // #include "format/juce_AudioPluginFormat.h" | |||
| // #include "format/juce_AudioPluginFormatManager.h" | |||
| // #include "scanning/juce_KnownPluginList.h" | |||
| // #include "utilities/juce_AudioProcessorValueTreeState.h" | |||
| // #include "utilities/juce_AudioProcessorParameterWithID.h" | |||
| // #include "utilities/juce_AudioParameterFloat.h" | |||
| // #include "utilities/juce_AudioParameterInt.h" | |||
| // #include "utilities/juce_AudioParameterBool.h" | |||
| // #include "utilities/juce_AudioParameterChoice.h" | |||
| #include "xml/juce_XmlElement.h" | |||
| #include "xml/juce_XmlDocument.h" | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| #include "juce_audio_graph.h" | |||
| #include <iostream> | |||
| using namespace juce2; | |||
| int main() | |||
| { | |||
| String x = "haha"; | |||
| std::cout << x << std::endl; | |||
| Atomic<float> a; | |||
| CharPointer_UTF8 c("c"); | |||
| HeapBlock<String> hs; | |||
| MemoryBlock m; | |||
| Array<CharPointer_UTF8> ar; | |||
| OwnedArray<String> ows; | |||
| AudioSampleBuffer as; | |||
| MidiBuffer mb; | |||
| MidiMessage ms; | |||
| AudioProcessorGraph g; | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,423 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||
| #define JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| A base class which provides methods for reference-counting. | |||
| 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<MyClass> 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<int> 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. | |||
| */ | |||
| void incReferenceCount() noexcept | |||
| { | |||
| ++refCount; | |||
| } | |||
| /** Decreases the object's reference count. | |||
| If the count gets to zero, the object will be deleted. | |||
| */ | |||
| void decReferenceCount() noexcept | |||
| { | |||
| jassert (getReferenceCount() > 0); | |||
| if (--refCount == 0) | |||
| delete this; | |||
| } | |||
| /** Decreases the object's reference count. | |||
| If the count gets to zero, the object will not be deleted, but this method | |||
| will return true, allowing the caller to take care of deletion. | |||
| */ | |||
| bool decReferenceCountWithoutDeleting() noexcept | |||
| { | |||
| jassert (getReferenceCount() > 0); | |||
| return --refCount == 0; | |||
| } | |||
| /** Returns the object's current reference count. */ | |||
| 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 <int> 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. | |||
| */ | |||
| void incReferenceCount() noexcept | |||
| { | |||
| ++refCount; | |||
| } | |||
| /** Decreases the object's reference count. | |||
| If the count gets to zero, the object will be deleted. | |||
| */ | |||
| void decReferenceCount() noexcept | |||
| { | |||
| jassert (getReferenceCount() > 0); | |||
| if (--refCount == 0) | |||
| delete this; | |||
| } | |||
| /** Decreases the object's reference count. | |||
| If the count gets to zero, the object will not be deleted, but this method | |||
| will return true, allowing the caller to take care of deletion. | |||
| */ | |||
| bool decReferenceCountWithoutDeleting() noexcept | |||
| { | |||
| jassert (getReferenceCount() > 0); | |||
| return --refCount == 0; | |||
| } | |||
| /** Returns the object's current reference count. */ | |||
| 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 | |||
| or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||
| class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||
| decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||
| should behave. | |||
| When using this class, you'll probably want to create a typedef to abbreviate the full | |||
| templated name - e.g. | |||
| @code | |||
| struct MyClass : public ReferenceCountedObject | |||
| { | |||
| typedef ReferenceCountedObjectPtr<MyClass> Ptr; | |||
| ... | |||
| @endcode | |||
| @see ReferenceCountedObject, ReferenceCountedObjectArray | |||
| */ | |||
| template <class ReferenceCountedObjectClass> | |||
| class ReferenceCountedObjectPtr | |||
| { | |||
| public: | |||
| /** The class being referenced by this pointer. */ | |||
| typedef ReferenceCountedObjectClass ReferencedType; | |||
| //============================================================================== | |||
| /** Creates a pointer to a null object. */ | |||
| ReferenceCountedObjectPtr() noexcept | |||
| : referencedObject (nullptr) | |||
| { | |||
| } | |||
| /** Creates a pointer to an object. | |||
| This will increment the object's reference-count. | |||
| */ | |||
| ReferenceCountedObjectPtr (ReferencedType* refCountedObject) noexcept | |||
| : referencedObject (refCountedObject) | |||
| { | |||
| incIfNotNull (refCountedObject); | |||
| } | |||
| #if JUCE_COMPILER_SUPPORTS_NULLPTR | |||
| /** Creates a pointer to a null object. */ | |||
| ReferenceCountedObjectPtr (decltype (nullptr)) noexcept | |||
| : referencedObject (nullptr) | |||
| { | |||
| } | |||
| #endif | |||
| /** Copies another pointer. | |||
| This will increment the object's reference-count. | |||
| */ | |||
| ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept | |||
| : referencedObject (other.referencedObject) | |||
| { | |||
| incIfNotNull (referencedObject); | |||
| } | |||
| /** Copies another pointer. | |||
| This will increment the object's reference-count (if it is non-null). | |||
| */ | |||
| template <typename Convertible> | |||
| ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr<Convertible>& other) noexcept | |||
| : referencedObject (static_cast<ReferencedType*> (other.get())) | |||
| { | |||
| incIfNotNull (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. | |||
| */ | |||
| 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 <typename Convertible> | |||
| ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr<Convertible>& other) | |||
| { | |||
| return operator= (static_cast<ReferencedType*> (other.get())); | |||
| } | |||
| /** 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= (ReferencedType* const newObject) | |||
| { | |||
| if (referencedObject != newObject) | |||
| { | |||
| incIfNotNull (newObject); | |||
| ReferencedType* const oldObject = referencedObject; | |||
| referencedObject = newObject; | |||
| decIfNotNull (oldObject); | |||
| } | |||
| return *this; | |||
| } | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| /** Takes-over the object from another pointer. */ | |||
| ReferenceCountedObjectPtr (ReferenceCountedObjectPtr&& other) noexcept | |||
| : referencedObject (other.referencedObject) | |||
| { | |||
| other.referencedObject = nullptr; | |||
| } | |||
| /** Takes-over the object from another pointer. */ | |||
| ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectPtr&& other) | |||
| { | |||
| std::swap (referencedObject, other.referencedObject); | |||
| return *this; | |||
| } | |||
| #endif | |||
| /** Destructor. | |||
| This will decrement the object's reference-count, which will cause the | |||
| object to be deleted when the ref-count hits zero. | |||
| */ | |||
| ~ReferenceCountedObjectPtr() | |||
| { | |||
| decIfNotNull (referencedObject); | |||
| } | |||
| //============================================================================== | |||
| /** Returns the object that this pointer references. | |||
| The pointer returned may be null, of course. | |||
| */ | |||
| operator ReferencedType*() const noexcept { return referencedObject; } | |||
| /** Returns the object that this pointer references. | |||
| The pointer returned may be null, of course. | |||
| */ | |||
| ReferencedType* get() const noexcept { return referencedObject; } | |||
| /** Returns the object that this pointer references. | |||
| The pointer returned may be null, of course. | |||
| */ | |||
| ReferencedType* getObject() const noexcept { return referencedObject; } | |||
| // the -> operator is called on the referenced object | |||
| ReferencedType* operator->() const noexcept | |||
| { | |||
| jassert (referencedObject != nullptr); // null pointer method call! | |||
| return referencedObject; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| ReferencedType* referencedObject; | |||
| static void incIfNotNull (ReferencedType* o) noexcept | |||
| { | |||
| if (o != nullptr) | |||
| o->incReferenceCount(); | |||
| } | |||
| static void decIfNotNull (ReferencedType* o) noexcept | |||
| { | |||
| if (o != nullptr && o->decReferenceCountWithoutDeleting()) | |||
| delete o; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** Compares two ReferenceCountedObjectPtrs. */ | |||
| template <typename ReferenceCountedObjectClass> | |||
| bool operator== (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, ReferenceCountedObjectClass* const object2) noexcept | |||
| { | |||
| return object1.get() == object2; | |||
| } | |||
| /** Compares two ReferenceCountedObjectPtrs. */ | |||
| template <typename ReferenceCountedObjectClass> | |||
| bool operator== (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
| { | |||
| return object1.get() == object2.get(); | |||
| } | |||
| /** Compares two ReferenceCountedObjectPtrs. */ | |||
| template <typename ReferenceCountedObjectClass> | |||
| bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
| { | |||
| return object1 == object2.get(); | |||
| } | |||
| /** Compares two ReferenceCountedObjectPtrs. */ | |||
| template <typename ReferenceCountedObjectClass> | |||
| bool operator!= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectClass* object2) noexcept | |||
| { | |||
| return object1.get() != object2; | |||
| } | |||
| /** Compares two ReferenceCountedObjectPtrs. */ | |||
| template <typename ReferenceCountedObjectClass> | |||
| bool operator!= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
| { | |||
| return object1.get() != object2.get(); | |||
| } | |||
| /** Compares two ReferenceCountedObjectPtrs. */ | |||
| template <typename ReferenceCountedObjectClass> | |||
| bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
| { | |||
| return object1 != object2.get(); | |||
| } | |||
| #endif // JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||
| @@ -0,0 +1,85 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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<String&&> (other.errorMessage)) | |||
| { | |||
| } | |||
| Result& Result::operator= (Result&& other) noexcept | |||
| { | |||
| errorMessage = static_cast<String&&> (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(); } | |||
| @@ -0,0 +1,127 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_RESULT_H_INCLUDED | |||
| #define JUCE_RESULT_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| @@ -71,7 +71,6 @@ public: | |||
| /** The actual processor object that this node represents. */ | |||
| AudioProcessor* getProcessor() const noexcept { return processor; } | |||
| #if 0 | |||
| /** A set of user-definable properties that are associated with this node. | |||
| This can be used to attach values to the node for whatever purpose seems | |||
| @@ -79,7 +78,6 @@ public: | |||
| is displaying the nodes on-screen. | |||
| */ | |||
| NamedValueSet properties; | |||
| #endif | |||
| //============================================================================== | |||
| /** A convenient typedef for referring to a pointer to a node object. */ | |||
| @@ -0,0 +1,58 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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; | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_FILEINPUTSOURCE_H_INCLUDED | |||
| #define JUCE_FILEINPUTSOURCE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| @@ -0,0 +1,86 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| int64 juce_fileSetPosition (void* handle, int64 pos); | |||
| //============================================================================== | |||
| FileInputStream::FileInputStream (const File& f) | |||
| : file (f), | |||
| fileHandle (nullptr), | |||
| currentPosition (0), | |||
| status (Result::ok()) | |||
| { | |||
| openHandle(); | |||
| } | |||
| int64 FileInputStream::getTotalLength() | |||
| { | |||
| // You should always check that a stream opened successfully before using it! | |||
| jassert (openedOk()); | |||
| return file.getSize(); | |||
| } | |||
| int FileInputStream::read (void* buffer, int bytesToRead) | |||
| { | |||
| // You should always check that a stream opened successfully before using it! | |||
| jassert (openedOk()); | |||
| // The buffer should never be null, and a negative size is probably a | |||
| // sign that something is broken! | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| const size_t num = readInternal (buffer, (size_t) bytesToRead); | |||
| currentPosition += (int64) num; | |||
| return (int) num; | |||
| } | |||
| bool FileInputStream::isExhausted() | |||
| { | |||
| return currentPosition >= getTotalLength(); | |||
| } | |||
| int64 FileInputStream::getPosition() | |||
| { | |||
| return currentPosition; | |||
| } | |||
| bool FileInputStream::setPosition (int64 pos) | |||
| { | |||
| // You should always check that a stream opened successfully before using it! | |||
| jassert (openedOk()); | |||
| if (pos != currentPosition) | |||
| currentPosition = juce_fileSetPosition (fileHandle, pos); | |||
| return currentPosition == pos; | |||
| } | |||
| @@ -0,0 +1,98 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_FILEINPUTSTREAM_H_INCLUDED | |||
| #define JUCE_FILEINPUTSTREAM_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| An input stream that reads from a local file. | |||
| @see InputStream, FileOutputStream, File::createInputStream | |||
| */ | |||
| class JUCE_API FileInputStream : public InputStream | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a FileInputStream to read from the given file. | |||
| After creating a FileInputStream, you should use openedOk() or failedToOpen() | |||
| to make sure that it's OK before trying to read from it! If it failed, you | |||
| can call getStatus() to get more error information. | |||
| */ | |||
| 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*, int) override; | |||
| bool isExhausted() override; | |||
| int64 getPosition() override; | |||
| bool setPosition (int64) override; | |||
| private: | |||
| //============================================================================== | |||
| const File file; | |||
| void* fileHandle; | |||
| int64 currentPosition; | |||
| Result status; | |||
| void openHandle(); | |||
| size_t readInternal (void*, size_t); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) | |||
| }; | |||
| #endif // JUCE_FILEINPUTSTREAM_H_INCLUDED | |||
| @@ -0,0 +1,80 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_INPUTSOURCE_H_INCLUDED | |||
| #define JUCE_INPUTSOURCE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| @@ -0,0 +1,79 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| Identifier::Identifier() noexcept {} | |||
| Identifier::~Identifier() noexcept {} | |||
| Identifier::Identifier (const Identifier& other) noexcept : name (other.name) {} | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| Identifier::Identifier (Identifier&& other) noexcept : name (static_cast<String&&> (other.name)) {} | |||
| Identifier& Identifier::operator= (Identifier&& other) noexcept | |||
| { | |||
| name = static_cast<String&&> (other.name); | |||
| return *this; | |||
| } | |||
| #endif | |||
| Identifier& Identifier::operator= (const Identifier& other) noexcept | |||
| { | |||
| name = other.name; | |||
| return *this; | |||
| } | |||
| Identifier::Identifier (const String& nm) | |||
| : name (StringPool::getGlobalPool().getPooledString (nm)) | |||
| { | |||
| // An Identifier cannot be created from an empty string! | |||
| jassert (nm.isNotEmpty()); | |||
| } | |||
| Identifier::Identifier (const char* nm) | |||
| : name (StringPool::getGlobalPool().getPooledString (nm)) | |||
| { | |||
| // An Identifier cannot be created from an empty string! | |||
| jassert (nm != nullptr && nm[0] != 0); | |||
| } | |||
| Identifier::Identifier (String::CharPointerType start, String::CharPointerType end) | |||
| : name (StringPool::getGlobalPool().getPooledString (start, end)) | |||
| { | |||
| // An Identifier cannot be created from an empty string! | |||
| jassert (start < end); | |||
| } | |||
| Identifier Identifier::null; | |||
| bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept | |||
| { | |||
| return possibleIdentifier.isNotEmpty() | |||
| && possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%"); | |||
| } | |||
| @@ -0,0 +1,142 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_IDENTIFIER_H_INCLUDED | |||
| #define JUCE_IDENTIFIER_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Represents a string identifier, designed for accessing properties by name. | |||
| Comparing two Identifier objects is very fast (an O(1) operation), but creating | |||
| them can be slower than just using a String directly, so the optimal way to use them | |||
| is to keep some static Identifier objects for the things you use often. | |||
| @see NamedValueSet, 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 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 (String::CharPointerType nameStart, String::CharPointerType nameEnd); | |||
| /** Creates a copy of another identifier. */ | |||
| Identifier (const Identifier& other) noexcept; | |||
| /** Creates a copy of another identifier. */ | |||
| Identifier& operator= (const Identifier& other) noexcept; | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| /** Creates a copy of another identifier. */ | |||
| Identifier (Identifier&& other) noexcept; | |||
| /** Creates a copy of another identifier. */ | |||
| Identifier& operator= (Identifier&& other) noexcept; | |||
| #endif | |||
| /** Destructor */ | |||
| ~Identifier() noexcept; | |||
| /** Compares two identifiers. This is a very fast operation. */ | |||
| inline bool operator== (const Identifier& other) const noexcept { return name.getCharPointer() == other.name.getCharPointer(); } | |||
| /** Compares two identifiers. This is a very fast operation. */ | |||
| inline bool operator!= (const Identifier& other) const noexcept { return name.getCharPointer() != other.name.getCharPointer(); } | |||
| /** Compares the identifier with a string. */ | |||
| inline bool operator== (StringRef other) const noexcept { return name == other; } | |||
| /** Compares the identifier with a string. */ | |||
| inline bool operator!= (StringRef other) const noexcept { return name != other; } | |||
| /** Compares the identifier with a string. */ | |||
| inline bool operator< (StringRef other) const noexcept { return name < other; } | |||
| /** Compares the identifier with a string. */ | |||
| inline bool operator<= (StringRef other) const noexcept { return name <= other; } | |||
| /** Compares the identifier with a string. */ | |||
| inline bool operator> (StringRef other) const noexcept { return name > other; } | |||
| /** Compares the identifier with a string. */ | |||
| inline bool operator>= (StringRef other) const noexcept { return name >= other; } | |||
| /** Returns this identifier as a string. */ | |||
| const String& toString() const noexcept { return name; } | |||
| /** Returns this identifier's raw string pointer. */ | |||
| operator String::CharPointerType() const noexcept { return name.getCharPointer(); } | |||
| /** Returns this identifier's raw string pointer. */ | |||
| String::CharPointerType getCharPointer() const noexcept { return name.getCharPointer(); } | |||
| /** Returns this identifier as a StringRef. */ | |||
| operator StringRef() const noexcept { return name; } | |||
| /** Returns true if this Identifier is not null */ | |||
| bool isValid() const noexcept { return name.isNotEmpty(); } | |||
| /** Returns true if this Identifier is null */ | |||
| bool isNull() const noexcept { return name.isEmpty(); } | |||
| /** 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 name; | |||
| }; | |||
| #endif // JUCE_IDENTIFIER_H_INCLUDED | |||
| @@ -0,0 +1,508 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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<Array <String>&&> (other.strings)) | |||
| { | |||
| } | |||
| #endif | |||
| StringArray::StringArray (const String& firstValue) | |||
| { | |||
| strings.add (firstValue); | |||
| } | |||
| StringArray::StringArray (const String* initialStrings, int numberOfStrings) | |||
| { | |||
| strings.addArray (initialStrings, numberOfStrings); | |||
| } | |||
| StringArray::StringArray (const char* const* initialStrings) | |||
| { | |||
| strings.addNullTerminatedArray (initialStrings); | |||
| } | |||
| StringArray::StringArray (const char* const* initialStrings, int numberOfStrings) | |||
| { | |||
| strings.addArray (initialStrings, numberOfStrings); | |||
| } | |||
| StringArray::StringArray (const wchar_t* const* initialStrings) | |||
| { | |||
| strings.addNullTerminatedArray (initialStrings); | |||
| } | |||
| StringArray::StringArray (const wchar_t* const* initialStrings, int numberOfStrings) | |||
| { | |||
| strings.addArray (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<Array<String>&&> (other.strings); | |||
| return *this; | |||
| } | |||
| #endif | |||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
| StringArray::StringArray (const std::initializer_list<const char*>& stringList) | |||
| { | |||
| strings.addArray (stringList); | |||
| } | |||
| #endif | |||
| StringArray::~StringArray() | |||
| { | |||
| } | |||
| bool StringArray::operator== (const StringArray& other) const noexcept | |||
| { | |||
| return strings == other.strings; | |||
| } | |||
| 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); | |||
| #if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
| return String::empty; | |||
| #else | |||
| static String empty; | |||
| return empty; | |||
| #endif | |||
| } | |||
| String& StringArray::getReference (const int index) noexcept | |||
| { | |||
| return strings.getReference (index); | |||
| } | |||
| void StringArray::add (const String& newString) | |||
| { | |||
| strings.add (newString); | |||
| } | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| void StringArray::add (String&& stringToAdd) | |||
| { | |||
| strings.add (static_cast<String&&> (stringToAdd)); | |||
| } | |||
| #endif | |||
| void StringArray::insert (const int index, const String& newString) | |||
| { | |||
| strings.insert (index, newString); | |||
| } | |||
| bool StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) | |||
| { | |||
| if (contains (newString, ignoreCase)) | |||
| return false; | |||
| add (newString); | |||
| return true; | |||
| } | |||
| 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::mergeArray (const StringArray& otherArray, const bool ignoreCase) | |||
| { | |||
| for (int i = 0; i < otherArray.size(); ++i) | |||
| addIfNotAlreadyThere (otherArray[i], ignoreCase); | |||
| } | |||
| void StringArray::set (const int index, const String& newString) | |||
| { | |||
| strings.set (index, newString); | |||
| } | |||
| bool StringArray::contains (StringRef stringToLookFor, const bool ignoreCase) const | |||
| { | |||
| return indexOf (stringToLookFor, ignoreCase) >= 0; | |||
| } | |||
| int StringArray::indexOf (StringRef stringToLookFor, const bool ignoreCase, int i) const | |||
| { | |||
| if (i < 0) | |||
| i = 0; | |||
| const int numElements = size(); | |||
| if (ignoreCase) | |||
| { | |||
| for (; i < numElements; ++i) | |||
| if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) | |||
| return i; | |||
| } | |||
| else | |||
| { | |||
| for (; i < numElements; ++i) | |||
| if (stringToLookFor == strings.getReference (i)) | |||
| return i; | |||
| } | |||
| return -1; | |||
| } | |||
| void StringArray::move (const int currentIndex, const int newIndex) noexcept | |||
| { | |||
| strings.move (currentIndex, newIndex); | |||
| } | |||
| //============================================================================== | |||
| void StringArray::remove (const int index) | |||
| { | |||
| strings.remove (index); | |||
| } | |||
| void StringArray::removeString (StringRef 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& s1, String& s2) noexcept { return s1.compare (s2); } | |||
| }; | |||
| struct InternalStringArrayComparator_CaseInsensitive | |||
| { | |||
| static int compareElements (String& s1, String& s2) noexcept { return s1.compareIgnoreCase (s2); } | |||
| }; | |||
| struct InternalStringArrayComparator_Natural | |||
| { | |||
| static int compareElements (String& s1, String& s2) noexcept { return s1.compareNatural (s2); } | |||
| }; | |||
| void StringArray::sort (const bool ignoreCase) | |||
| { | |||
| if (ignoreCase) | |||
| { | |||
| InternalStringArrayComparator_CaseInsensitive comp; | |||
| strings.sort (comp); | |||
| } | |||
| else | |||
| { | |||
| InternalStringArrayComparator_CaseSensitive comp; | |||
| strings.sort (comp); | |||
| } | |||
| } | |||
| void StringArray::sortNatural() | |||
| { | |||
| InternalStringArrayComparator_Natural comp; | |||
| strings.sort (comp); | |||
| } | |||
| //============================================================================== | |||
| String StringArray::joinIntoString (StringRef 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(); | |||
| if (start == last - 1) | |||
| return strings.getReference (start); | |||
| const size_t separatorBytes = separator.text.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.text); | |||
| } | |||
| dest.writeNull(); | |||
| return result; | |||
| } | |||
| int StringArray::addTokens (StringRef text, const bool preserveQuotedStrings) | |||
| { | |||
| return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); | |||
| } | |||
| int StringArray::addTokens (StringRef text, StringRef breakCharacters, StringRef quoteCharacters) | |||
| { | |||
| int num = 0; | |||
| if (text.isNotEmpty()) | |||
| { | |||
| for (String::CharPointerType t (text.text);;) | |||
| { | |||
| String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t, | |||
| breakCharacters.text, | |||
| quoteCharacters.text)); | |||
| strings.add (String (t, tokenEnd)); | |||
| ++num; | |||
| if (tokenEnd.isEmpty()) | |||
| break; | |||
| t = ++tokenEnd; | |||
| } | |||
| } | |||
| return num; | |||
| } | |||
| int StringArray::addLines (StringRef sourceText) | |||
| { | |||
| int numLines = 0; | |||
| String::CharPointerType text (sourceText.text); | |||
| 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 (StringRef stringToTokenise, bool preserveQuotedStrings) | |||
| { | |||
| StringArray s; | |||
| s.addTokens (stringToTokenise, preserveQuotedStrings); | |||
| return s; | |||
| } | |||
| StringArray StringArray::fromTokens (StringRef stringToTokenise, | |||
| StringRef breakCharacters, | |||
| StringRef quoteCharacters) | |||
| { | |||
| StringArray s; | |||
| s.addTokens (stringToTokenise, breakCharacters, quoteCharacters); | |||
| return s; | |||
| } | |||
| StringArray StringArray::fromLines (StringRef 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)); | |||
| for (int nextIndex = i + 1;;) | |||
| { | |||
| 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(); | |||
| } | |||
| @@ -0,0 +1,441 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_STRINGARRAY_H_INCLUDED | |||
| #define JUCE_STRINGARRAY_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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&); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| StringArray (StringArray&&) 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); | |||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
| StringArray (const std::initializer_list<const char*>& strings); | |||
| #endif | |||
| /** Destructor. */ | |||
| ~StringArray(); | |||
| /** Copies the contents of another string array into this one */ | |||
| StringArray& operator= (const StringArray&); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| StringArray& operator= (StringArray&&) noexcept; | |||
| #endif | |||
| /** Swaps the contents of this and another StringArray. */ | |||
| void swapWith (StringArray&) 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&) 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&) const noexcept; | |||
| //============================================================================== | |||
| /** Returns the number of strings in the array */ | |||
| inline int size() const noexcept { return strings.size(); } | |||
| /** Returns true if the array is empty, false otherwise. */ | |||
| inline bool isEmpty() const noexcept { return size() == 0; } | |||
| /** 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 (StringRef 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 (StringRef stringToLookFor, | |||
| bool ignoreCase = false, | |||
| int startIndex = 0) const; | |||
| //============================================================================== | |||
| /** Appends a string at the end of the array. */ | |||
| void add (const String& stringToAdd); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| /** Appends a string at the end of the array. */ | |||
| void add (String&& stringToAdd); | |||
| #endif | |||
| /** 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. | |||
| @return true if the string has been added, false otherwise. | |||
| */ | |||
| bool 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); | |||
| /** Merges the strings from another array into this one. | |||
| This will not add a string that already exists. | |||
| @param other the array to add | |||
| @param ignoreCase ignore case when merging | |||
| */ | |||
| void mergeArray (const StringArray& other, | |||
| bool ignoreCase = false); | |||
| /** 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 (StringRef 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 (StringRef stringToTokenise, | |||
| StringRef breakCharacters, | |||
| StringRef 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 (StringRef 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 the parsed tokens as an array. | |||
| @see addTokens | |||
| */ | |||
| static StringArray fromTokens (StringRef stringToTokenise, | |||
| bool preserveQuotedStrings); | |||
| /** Returns an array containing the tokens in a given string. | |||
| This will tokenise the given string using the breakCharacters string to define | |||
| the token delimiters, and will return the parsed 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 (StringRef stringToTokenise, | |||
| StringRef breakCharacters, | |||
| StringRef 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 (StringRef 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 all occurrences of the given string from the array. | |||
| The comparison may be case-insensitive depending on the ignoreCase parameter. | |||
| */ | |||
| void removeString (StringRef 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 nullptr, 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 nullptr, 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 (StringRef 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); | |||
| /** Sorts the array using extra language-aware rules to do a better job of comparing | |||
| words containing spaces and numbers. | |||
| @see String::compareNatural() | |||
| */ | |||
| void sortNatural(); | |||
| //============================================================================== | |||
| /** 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(); | |||
| /** This is the array holding the actual strings. This is public to allow direct access | |||
| to array methods that may not already be provided by the StringArray class. | |||
| */ | |||
| Array<String> strings; | |||
| private: | |||
| JUCE_LEAK_DETECTOR (StringArray) | |||
| }; | |||
| #endif // JUCE_STRINGARRAY_H_INCLUDED | |||
| @@ -0,0 +1,168 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| static const int minNumberOfStringsForGarbageCollection = 300; | |||
| static const uint32 garbageCollectionInterval = 30000; | |||
| StringPool::StringPool() noexcept : lastGarbageCollectionTime (0) {} | |||
| StringPool::~StringPool() {} | |||
| struct StartEndString | |||
| { | |||
| StartEndString (String::CharPointerType s, String::CharPointerType e) noexcept : start (s), end (e) {} | |||
| operator String() const { return String (start, end); } | |||
| String::CharPointerType start, end; | |||
| }; | |||
| static int compareStrings (const String& s1, const String& s2) noexcept { return s1.compare (s2); } | |||
| static int compareStrings (CharPointer_UTF8 s1, const String& s2) noexcept { return s1.compare (s2.getCharPointer()); } | |||
| static int compareStrings (const StartEndString& string1, const String& string2) noexcept | |||
| { | |||
| String::CharPointerType s1 (string1.start), s2 (string2.getCharPointer()); | |||
| for (;;) | |||
| { | |||
| const int c1 = s1 < string1.end ? (int) s1.getAndAdvance() : 0; | |||
| 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; | |||
| } | |||
| template <typename NewStringType> | |||
| static String addPooledString (Array<String>& strings, const NewStringType& newString) | |||
| { | |||
| int start = 0; | |||
| int end = strings.size(); | |||
| while (start < end) | |||
| { | |||
| const String& startString = strings.getReference (start); | |||
| const int startComp = compareStrings (newString, startString); | |||
| if (startComp == 0) | |||
| return startString; | |||
| const int halfway = (start + end) / 2; | |||
| if (halfway == start) | |||
| { | |||
| if (startComp > 0) | |||
| ++start; | |||
| break; | |||
| } | |||
| const String& halfwayString = strings.getReference (halfway); | |||
| const int halfwayComp = compareStrings (newString, halfwayString); | |||
| if (halfwayComp == 0) | |||
| return halfwayString; | |||
| if (halfwayComp > 0) | |||
| start = halfway; | |||
| else | |||
| end = halfway; | |||
| } | |||
| strings.insert (start, newString); | |||
| return strings.getReference (start); | |||
| } | |||
| String StringPool::getPooledString (const char* const newString) | |||
| { | |||
| if (newString == nullptr || *newString == 0) | |||
| return String(); | |||
| const ScopedLock sl (lock); | |||
| garbageCollectIfNeeded(); | |||
| return addPooledString (strings, CharPointer_UTF8 (newString)); | |||
| } | |||
| String StringPool::getPooledString (String::CharPointerType start, String::CharPointerType end) | |||
| { | |||
| if (start.isEmpty() || start == end) | |||
| return String(); | |||
| const ScopedLock sl (lock); | |||
| garbageCollectIfNeeded(); | |||
| return addPooledString (strings, StartEndString (start, end)); | |||
| } | |||
| String StringPool::getPooledString (StringRef newString) | |||
| { | |||
| if (newString.isEmpty()) | |||
| return String(); | |||
| const ScopedLock sl (lock); | |||
| garbageCollectIfNeeded(); | |||
| return addPooledString (strings, newString.text); | |||
| } | |||
| String StringPool::getPooledString (const String& newString) | |||
| { | |||
| if (newString.isEmpty()) | |||
| return String(); | |||
| const ScopedLock sl (lock); | |||
| garbageCollectIfNeeded(); | |||
| return addPooledString (strings, newString); | |||
| } | |||
| void StringPool::garbageCollectIfNeeded() | |||
| { | |||
| if (strings.size() > minNumberOfStringsForGarbageCollection | |||
| && Time::getApproximateMillisecondCounter() > lastGarbageCollectionTime + garbageCollectionInterval) | |||
| garbageCollect(); | |||
| } | |||
| void StringPool::garbageCollect() | |||
| { | |||
| const ScopedLock sl (lock); | |||
| for (int i = strings.size(); --i >= 0;) | |||
| if (strings.getReference(i).getReferenceCount() == 1) | |||
| strings.remove (i); | |||
| lastGarbageCollectionTime = Time::getApproximateMillisecondCounter(); | |||
| } | |||
| StringPool& StringPool::getGlobalPool() noexcept | |||
| { | |||
| static StringPool pool; | |||
| return pool; | |||
| } | |||
| @@ -0,0 +1,98 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_STRINGPOOL_H_INCLUDED | |||
| #define JUCE_STRINGPOOL_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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 shared copy of the string that is passed in. | |||
| The pool will always return the same String object when asked for a string that matches it. | |||
| */ | |||
| String getPooledString (const String& original); | |||
| /** Returns a pointer to a copy of the string that is passed in. | |||
| The pool will always return the same String object when asked for a string that matches it. | |||
| */ | |||
| String getPooledString (const char* original); | |||
| /** Returns a pointer to a shared copy of the string that is passed in. | |||
| The pool will always return the same String object when asked for a string that matches it. | |||
| */ | |||
| String getPooledString (StringRef original); | |||
| /** Returns a pointer to a copy of the string that is passed in. | |||
| The pool will always return the same String object when asked for a string that matches it. | |||
| */ | |||
| String getPooledString (String::CharPointerType start, String::CharPointerType end); | |||
| //============================================================================== | |||
| /** Scans the pool, and removes any strings that are unreferenced. | |||
| You don't generally need to call this - it'll be called automatically when the pool grows | |||
| large enough to warrant it. | |||
| */ | |||
| void garbageCollect(); | |||
| /** Returns a shared global pool which is used for things like Identifiers, XML parsing. */ | |||
| static StringPool& getGlobalPool() noexcept; | |||
| private: | |||
| Array<String> strings; | |||
| CarlaRecursiveMutex lock; | |||
| uint32 lastGarbageCollectionTime; | |||
| void garbageCollectIfNeeded(); | |||
| JUCE_DECLARE_NON_COPYABLE (StringPool) | |||
| }; | |||
| #endif // JUCE_STRINGPOOL_H_INCLUDED | |||
| @@ -0,0 +1,895 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| 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); | |||
| }*/ | |||
| static String::CharPointerType findEndOfToken (String::CharPointerType p) | |||
| { | |||
| while (isIdentifierChar (*p)) | |||
| ++p; | |||
| return p; | |||
| } | |||
| } | |||
| XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) | |||
| { | |||
| if (originalText.isEmpty() && inputSource != nullptr) | |||
| { | |||
| ScopedPointer<InputStream> in (inputSource->createInputStream()); | |||
| if (in != nullptr) | |||
| { | |||
| MemoryOutputStream data; | |||
| data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); | |||
| #if JUCE_STRING_UTF_TYPE == 8 | |||
| if (data.getDataSize() > 2) | |||
| { | |||
| data.writeByte (0); | |||
| const char* text = static_cast<const char*> (data.getData()); | |||
| if (CharPointer_UTF16::isByteOrderMarkBigEndian (text) | |||
| || CharPointer_UTF16::isByteOrderMarkLittleEndian (text)) | |||
| { | |||
| originalText = data.toString(); | |||
| } | |||
| else | |||
| { | |||
| if (CharPointer_UTF8::isByteOrderMark (text)) | |||
| text += 3; | |||
| // parse the input buffer directly to avoid copying it all to a string.. | |||
| return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement); | |||
| } | |||
| } | |||
| #else | |||
| originalText = data.toString(); | |||
| #endif | |||
| } | |||
| } | |||
| return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement); | |||
| } | |||
| 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<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted())); | |||
| if (in != nullptr) | |||
| return in->readEntireStreamAsString(); | |||
| } | |||
| return String(); | |||
| } | |||
| juce_wchar XmlDocument::readNextChar() noexcept | |||
| { | |||
| const juce_wchar c = input.getAndAdvance(); | |||
| if (c == 0) | |||
| { | |||
| outOfData = true; | |||
| --input; | |||
| } | |||
| return c; | |||
| } | |||
| XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToParse, | |||
| const bool onlyReadOuterDocumentElement) | |||
| { | |||
| input = textToParse; | |||
| errorOccurred = false; | |||
| outOfData = false; | |||
| needToLoadDTD = true; | |||
| if (textToParse.isEmpty()) | |||
| { | |||
| lastError = "not enough input"; | |||
| } | |||
| else if (! parseHeader()) | |||
| { | |||
| lastError = "malformed header"; | |||
| } | |||
| else if (! parseDTD()) | |||
| { | |||
| lastError = "malformed DTD"; | |||
| } | |||
| else | |||
| { | |||
| lastError.clear(); | |||
| ScopedPointer<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement)); | |||
| if (! errorOccurred) | |||
| return result.release(); | |||
| } | |||
| return nullptr; | |||
| } | |||
| bool XmlDocument::parseHeader() | |||
| { | |||
| skipNextWhiteSpace(); | |||
| if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<?xml"), 5) == 0) | |||
| { | |||
| const String::CharPointerType headerEnd (CharacterFunctions::find (input, CharPointer_UTF8 ("?>"))); | |||
| if (headerEnd.isEmpty()) | |||
| return false; | |||
| #if JUCE_DEBUG | |||
| const String encoding (String (input, headerEnd) | |||
| .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(); | |||
| } | |||
| return true; | |||
| } | |||
| bool XmlDocument::parseDTD() | |||
| { | |||
| if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<!DOCTYPE"), 9) == 0) | |||
| { | |||
| input += 9; | |||
| const String::CharPointerType dtdStart (input); | |||
| for (int n = 1; n > 0;) | |||
| { | |||
| const juce_wchar c = readNextChar(); | |||
| if (outOfData) | |||
| return false; | |||
| if (c == '<') | |||
| ++n; | |||
| else if (c == '>') | |||
| --n; | |||
| } | |||
| dtdText = String (dtdStart, input - 1).trim(); | |||
| } | |||
| return true; | |||
| } | |||
| void XmlDocument::skipNextWhiteSpace() | |||
| { | |||
| for (;;) | |||
| { | |||
| input = input.findEndOfWhitespace(); | |||
| if (input.isEmpty()) | |||
| { | |||
| outOfData = true; | |||
| break; | |||
| } | |||
| if (*input == '<') | |||
| { | |||
| 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; | |||
| } | |||
| 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); | |||
| for (;;) | |||
| { | |||
| const juce_wchar character = *input; | |||
| if (character == quote) | |||
| { | |||
| result.appendCharPointer (start, input); | |||
| ++input; | |||
| return; | |||
| } | |||
| else if (character == '&') | |||
| { | |||
| result.appendCharPointer (start, input); | |||
| break; | |||
| } | |||
| else if (character == 0) | |||
| { | |||
| setLastError ("unmatched quotes", false); | |||
| outOfData = true; | |||
| break; | |||
| } | |||
| ++input; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||
| { | |||
| XmlElement* node = nullptr; | |||
| skipNextWhiteSpace(); | |||
| if (outOfData) | |||
| return nullptr; | |||
| if (*input == '<') | |||
| { | |||
| ++input; | |||
| String::CharPointerType endOfToken (XmlIdentifierChars::findEndOfToken (input)); | |||
| if (endOfToken == input) | |||
| { | |||
| // no tag name - but allow for a gap after the '<' before giving an error | |||
| skipNextWhiteSpace(); | |||
| endOfToken = XmlIdentifierChars::findEndOfToken (input); | |||
| if (endOfToken == input) | |||
| { | |||
| setLastError ("tag name missing", false); | |||
| return node; | |||
| } | |||
| } | |||
| node = new XmlElement (input, endOfToken); | |||
| input = endOfToken; | |||
| LinkedListPointer<XmlElement::XmlAttributeNode>::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)) | |||
| { | |||
| String::CharPointerType attNameEnd (XmlIdentifierChars::findEndOfToken (input)); | |||
| if (attNameEnd != input) | |||
| { | |||
| const String::CharPointerType attNameStart (input); | |||
| input = attNameEnd; | |||
| skipNextWhiteSpace(); | |||
| if (readNextChar() == '=') | |||
| { | |||
| skipNextWhiteSpace(); | |||
| const juce_wchar nextChar = *input; | |||
| if (nextChar == '"' || nextChar == '\'') | |||
| { | |||
| XmlElement::XmlAttributeNode* const newAtt | |||
| = new XmlElement::XmlAttributeNode (attNameStart, attNameEnd); | |||
| readQuotedString (newAtt->value); | |||
| attributeAppender.append (newAtt); | |||
| continue; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| setLastError ("expected '=' after attribute '" | |||
| + String (attNameStart, attNameEnd) + "'", false); | |||
| return node; | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (! outOfData) | |||
| setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| return node; | |||
| } | |||
| void XmlDocument::readChildElements (XmlElement& parent) | |||
| { | |||
| LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement); | |||
| for (;;) | |||
| { | |||
| const String::CharPointerType preWhitespaceInput (input); | |||
| skipNextWhiteSpace(); | |||
| if (outOfData) | |||
| { | |||
| setLastError ("unmatched tags", false); | |||
| break; | |||
| } | |||
| if (*input == '<') | |||
| { | |||
| const juce_wchar c1 = input[1]; | |||
| if (c1 == '/') | |||
| { | |||
| // our close tag.. | |||
| const int closeTag = input.indexOf ((juce_wchar) '>'); | |||
| if (closeTag >= 0) | |||
| input += closeTag + 1; | |||
| break; | |||
| } | |||
| if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_UTF8 ("[CDATA["), 7) == 0) | |||
| { | |||
| input += 9; | |||
| const String::CharPointerType inputStart (input); | |||
| for (;;) | |||
| { | |||
| const juce_wchar c0 = *input; | |||
| if (c0 == 0) | |||
| { | |||
| setLastError ("unterminated CDATA section", false); | |||
| outOfData = true; | |||
| break; | |||
| } | |||
| else if (c0 == ']' | |||
| && input[1] == ']' | |||
| && input[2] == '>') | |||
| { | |||
| childAppender.append (XmlElement::createTextElement (String (inputStart, input))); | |||
| input += 3; | |||
| break; | |||
| } | |||
| ++input; | |||
| } | |||
| } | |||
| 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 | |||
| MemoryOutputStream textElementContent; | |||
| bool contentShouldBeUsed = ! ignoreEmptyTextElements; | |||
| for (;;) | |||
| { | |||
| const juce_wchar c = *input; | |||
| if (c == '<') | |||
| { | |||
| if (input[1] == '!' && input[2] == '-' && input[3] == '-') | |||
| { | |||
| input += 4; | |||
| const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); | |||
| if (closeComment < 0) | |||
| { | |||
| setLastError ("unterminated comment", false); | |||
| outOfData = true; | |||
| return; | |||
| } | |||
| input += closeComment + 3; | |||
| continue; | |||
| } | |||
| 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; | |||
| while (XmlElement* n = readNextElement (true)) | |||
| childAppender.append (n); | |||
| input = oldInput; | |||
| outOfData = oldOutOfData; | |||
| } | |||
| else | |||
| { | |||
| textElementContent << entity; | |||
| contentShouldBeUsed = contentShouldBeUsed || entity.containsNonWhitespaceChars(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (;; ++input) | |||
| { | |||
| juce_wchar nextChar = *input; | |||
| if (nextChar == '\r') | |||
| { | |||
| nextChar = '\n'; | |||
| if (input[1] == '\n') | |||
| continue; | |||
| } | |||
| if (nextChar == '<' || nextChar == '&') | |||
| break; | |||
| if (nextChar == 0) | |||
| { | |||
| setLastError ("unmatched tags", false); | |||
| outOfData = true; | |||
| return; | |||
| } | |||
| textElementContent.appendUTF8Char (nextChar); | |||
| contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar); | |||
| } | |||
| } | |||
| } | |||
| if (contentShouldBeUsed) | |||
| childAppender.append (XmlElement::createTextElement (textElementContent.toUTF8())); | |||
| } | |||
| } | |||
| } | |||
| 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; | |||
| } | |||
| #if 0 // FIXME | |||
| result << (juce_wchar) charCode; | |||
| #endif | |||
| } | |||
| 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<juce_wchar> (ent.substring (2).getHexValue32())); | |||
| if (char1 >= '0' && char1 <= '9') | |||
| return String::charToString (static_cast<juce_wchar> (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 ("<!entity")) | |||
| { | |||
| String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">").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 ("<!entity")) | |||
| { | |||
| const String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">")); | |||
| if (ent.equalsIgnoreCase ("system")) | |||
| return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); | |||
| return ent.trim().unquoted(); | |||
| } | |||
| } | |||
| return entity; | |||
| } | |||
| @@ -0,0 +1,183 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_XMLDOCUMENT_H_INCLUDED | |||
| #define JUCE_XMLDOCUMENT_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| 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")); | |||
| ScopedPointer<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 | |||
| ScopedPointer<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> inputSource; | |||
| XmlElement* parseDocumentElement (String::CharPointerType, bool outer); | |||
| void setLastError (const String&, bool carryOn); | |||
| bool parseHeader(); | |||
| bool parseDTD(); | |||
| void skipNextWhiteSpace(); | |||
| juce_wchar readNextChar() noexcept; | |||
| XmlElement* readNextElement (bool alsoParseSubElements); | |||
| void readChildElements (XmlElement&); | |||
| void readQuotedString (String&); | |||
| void readEntity (String&); | |||
| String getFileContents (const String&) const; | |||
| String expandEntity (const String&); | |||
| String expandExternalEntity (const String&); | |||
| String getParameterEntity (const String&); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) | |||
| }; | |||
| #endif // JUCE_XMLDOCUMENT_H_INCLUDED | |||
| @@ -0,0 +1,942 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| namespace | |||
| { | |||
| inline bool isValidXmlNameStartCharacter (const juce_wchar character) noexcept | |||
| { | |||
| return character == ':' | |||
| || character == '_' | |||
| || (character >= 'a' && character <= 'z') | |||
| || (character >= 'A' && character <= 'Z') | |||
| || (character >= 0xc0 && character <= 0xd6) | |||
| || (character >= 0xd8 && character <= 0xf6) | |||
| || (character >= 0xf8 && character <= 0x2ff) | |||
| || (character >= 0x370 && character <= 0x37d) | |||
| || (character >= 0x37f && character <= 0x1fff) | |||
| || (character >= 0x200c && character <= 0x200d) | |||
| || (character >= 0x2070 && character <= 0x218f) | |||
| || (character >= 0x2c00 && character <= 0x2fef) | |||
| || (character >= 0x3001 && character <= 0xd7ff) | |||
| || (character >= 0xf900 && character <= 0xfdcf) | |||
| || (character >= 0xfdf0 && character <= 0xfffd) | |||
| || (character >= 0x10000 && character <= 0xeffff); | |||
| } | |||
| inline bool isValidXmlNameBodyCharacter (const juce_wchar character) noexcept | |||
| { | |||
| return isValidXmlNameStartCharacter (character) | |||
| || character == '-' | |||
| || character == '.' | |||
| || character == 0xb7 | |||
| || (character >= '0' && character <= '9') | |||
| || (character >= 0x300 && character <= 0x036f) | |||
| || (character >= 0x203f && character <= 0x2040); | |||
| } | |||
| } | |||
| XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept | |||
| : name (other.name), | |||
| value (other.value) | |||
| { | |||
| } | |||
| XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept | |||
| : name (n), value (v) | |||
| { | |||
| jassert (isValidXmlName (name)); | |||
| } | |||
| XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd) | |||
| : name (nameStart, nameEnd) | |||
| { | |||
| jassert (isValidXmlName (name)); | |||
| } | |||
| //============================================================================== | |||
| XmlElement::XmlElement (const String& tag) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
| { | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (const char* tag) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
| { | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (StringRef tag) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
| { | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (const Identifier& tag) | |||
| : tagName (tag.toString()) | |||
| { | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd)) | |||
| { | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| 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<LinkedListPointer<XmlElement>&&> (other.nextListItem)), | |||
| firstChildElement (static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement)), | |||
| attributes (static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes)), | |||
| tagName (static_cast<String&&> (other.tagName)) | |||
| { | |||
| } | |||
| XmlElement& XmlElement::operator= (XmlElement&& other) noexcept | |||
| { | |||
| jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||
| removeAllAttributes(); | |||
| deleteAllChildElements(); | |||
| nextListItem = static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem); | |||
| firstChildElement = static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement); | |||
| attributes = static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes); | |||
| tagName = static_cast<String&&> (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 size_t numSpaces) | |||
| { | |||
| out.writeRepeatedByte (' ', numSpaces); | |||
| } | |||
| } | |||
| void XmlElement::writeElementAsText (OutputStream& outputStream, | |||
| const int indentationLevel, | |||
| const int lineWrapLength) const | |||
| { | |||
| using namespace XmlOutputFunctions; | |||
| if (indentationLevel >= 0) | |||
| writeSpaces (outputStream, (size_t) indentationLevel); | |||
| if (! isTextElement()) | |||
| { | |||
| outputStream.writeByte ('<'); | |||
| outputStream << tagName; | |||
| { | |||
| const size_t attIndent = (size_t) (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, (size_t) indentationLevel); | |||
| } | |||
| outputStream.write ("</", 2); | |||
| outputStream << tagName; | |||
| outputStream.writeByte ('>'); | |||
| } | |||
| else | |||
| { | |||
| outputStream.write ("/>", 2); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| escapeIllegalXmlChars (outputStream, getText(), false); | |||
| } | |||
| } | |||
| String XmlElement::createDocument (StringRef dtdToUse, | |||
| const bool allOnOneLine, | |||
| const bool includeXmlHeader, | |||
| StringRef encodingType, | |||
| const int lineWrapLength) const | |||
| { | |||
| MemoryOutputStream mem (2048); | |||
| writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); | |||
| return mem.toUTF8(); | |||
| } | |||
| void XmlElement::writeToStream (OutputStream& output, | |||
| StringRef dtdToUse, | |||
| const bool allOnOneLine, | |||
| const bool includeXmlHeader, | |||
| StringRef encodingType, | |||
| const int lineWrapLength) const | |||
| { | |||
| using namespace XmlOutputFunctions; | |||
| if (includeXmlHeader) | |||
| { | |||
| output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>"; | |||
| 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; | |||
| } | |||
| #if 0 | |||
| bool XmlElement::writeToFile (const File& file, | |||
| StringRef dtdToUse, | |||
| StringRef encodingType, | |||
| const int lineWrapLength) const | |||
| { | |||
| TemporaryFile tempFile (file); | |||
| { | |||
| FileOutputStream out (tempFile.getFile()); | |||
| if (! out.openedOk()) | |||
| return false; | |||
| writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength); | |||
| out.flush(); // (called explicitly to force an fsync on posix) | |||
| if (out.getStatus().failed()) | |||
| return false; | |||
| } | |||
| return tempFile.overwriteTargetFileWithTemporary(); | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| bool XmlElement::hasTagName (StringRef 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 (StringRef possibleTagName) const | |||
| { | |||
| return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName; | |||
| } | |||
| XmlElement* XmlElement::getNextElementWithTagName (StringRef requiredTagName) const | |||
| { | |||
| XmlElement* e = nextListItem; | |||
| while (e != nullptr && ! e->hasTagName (requiredTagName)) | |||
| e = e->nextListItem; | |||
| return e; | |||
| } | |||
| //============================================================================== | |||
| int XmlElement::getNumAttributes() const noexcept | |||
| { | |||
| return attributes.size(); | |||
| } | |||
| static const String& getEmptyStringRef() noexcept | |||
| { | |||
| #if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
| return String::empty; | |||
| #else | |||
| static String empty; | |||
| return empty; | |||
| #endif | |||
| } | |||
| const String& XmlElement::getAttributeName (const int index) const noexcept | |||
| { | |||
| if (const XmlAttributeNode* const att = attributes [index]) | |||
| return att->name.toString(); | |||
| return getEmptyStringRef(); | |||
| } | |||
| const String& XmlElement::getAttributeValue (const int index) const noexcept | |||
| { | |||
| if (const XmlAttributeNode* const att = attributes [index]) | |||
| return att->value; | |||
| return getEmptyStringRef(); | |||
| } | |||
| XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept | |||
| { | |||
| for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||
| if (att->name == attributeName) | |||
| return att; | |||
| return nullptr; | |||
| } | |||
| bool XmlElement::hasAttribute (StringRef attributeName) const noexcept | |||
| { | |||
| return getAttribute (attributeName) != nullptr; | |||
| } | |||
| //============================================================================== | |||
| const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept | |||
| { | |||
| if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
| return att->value; | |||
| return getEmptyStringRef(); | |||
| } | |||
| String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const | |||
| { | |||
| if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
| return att->value; | |||
| return defaultReturnValue; | |||
| } | |||
| int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const | |||
| { | |||
| if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
| return att->value.getIntValue(); | |||
| return defaultReturnValue; | |||
| } | |||
| double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const | |||
| { | |||
| if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
| return att->value.getDoubleValue(); | |||
| return defaultReturnValue; | |||
| } | |||
| bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const | |||
| { | |||
| if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
| { | |||
| const juce_wchar firstChar = *(att->value.getCharPointer().findEndOfWhitespace()); | |||
| return firstChar == '1' | |||
| || firstChar == 't' | |||
| || firstChar == 'y' | |||
| || firstChar == 'T' | |||
| || firstChar == 'Y'; | |||
| } | |||
| return defaultReturnValue; | |||
| } | |||
| bool XmlElement::compareAttribute (StringRef attributeName, | |||
| StringRef stringToCompareAgainst, | |||
| const bool ignoreCase) const noexcept | |||
| { | |||
| if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
| return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst) | |||
| : att->value == stringToCompareAgainst; | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| void XmlElement::setAttribute (const Identifier& attributeName, const String& value) | |||
| { | |||
| if (attributes == nullptr) | |||
| { | |||
| attributes = new XmlAttributeNode (attributeName, value); | |||
| } | |||
| else | |||
| { | |||
| for (XmlAttributeNode* att = attributes; ; att = att->nextListItem) | |||
| { | |||
| if (att->name == attributeName) | |||
| { | |||
| att->value = value; | |||
| break; | |||
| } | |||
| if (att->nextListItem == nullptr) | |||
| { | |||
| att->nextListItem = new XmlAttributeNode (attributeName, value); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void XmlElement::setAttribute (const Identifier& attributeName, const int number) | |||
| { | |||
| setAttribute (attributeName, String (number)); | |||
| } | |||
| void XmlElement::setAttribute (const Identifier& attributeName, const double number) | |||
| { | |||
| setAttribute (attributeName, String (number, 20)); | |||
| } | |||
| void XmlElement::removeAttribute (const Identifier& attributeName) noexcept | |||
| { | |||
| for (LinkedListPointer<XmlAttributeNode>* att = &attributes; | |||
| att->get() != nullptr; | |||
| att = &(att->get()->nextListItem)) | |||
| { | |||
| if (att->get()->name == 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 (StringRef childName) const noexcept | |||
| { | |||
| jassert (! childName.isEmpty()); | |||
| for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
| if (child->hasTagName (childName)) | |||
| return child; | |||
| return nullptr; | |||
| } | |||
| XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept | |||
| { | |||
| jassert (! attributeName.isEmpty()); | |||
| for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
| if (child->compareAttribute (attributeName, attributeValue)) | |||
| return child; | |||
| return nullptr; | |||
| } | |||
| void XmlElement::addChildElement (XmlElement* const newNode) noexcept | |||
| { | |||
| if (newNode != nullptr) | |||
| { | |||
| // The element being added must not be a child of another node! | |||
| jassert (newNode->nextListItem == nullptr); | |||
| firstChildElement.append (newNode); | |||
| } | |||
| } | |||
| void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept | |||
| { | |||
| if (newNode != nullptr) | |||
| { | |||
| // The element being added must not be a child of another node! | |||
| jassert (newNode->nextListItem == nullptr); | |||
| firstChildElement.insertAtIndex (indexToInsertAt, newNode); | |||
| } | |||
| } | |||
| void XmlElement::prependChildElement (XmlElement* newNode) noexcept | |||
| { | |||
| if (newNode != nullptr) | |||
| { | |||
| // The element being added must not be a child of another node! | |||
| jassert (newNode->nextListItem == nullptr); | |||
| firstChildElement.insertNext (newNode); | |||
| } | |||
| } | |||
| XmlElement* XmlElement::createNewChildElement (StringRef 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<XmlElement>* 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 nullptr, 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 (StringRef 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(); | |||
| if (getNumChildElements() == 1) | |||
| return firstChildElement.get()->getAllSubText(); | |||
| MemoryOutputStream mem (1024); | |||
| for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
| mem << child->getAllSubText(); | |||
| return mem.toUTF8(); | |||
| } | |||
| String XmlElement::getChildElementAllSubText (StringRef 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; | |||
| } | |||
| bool XmlElement::isValidXmlName (StringRef text) noexcept | |||
| { | |||
| if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance())) | |||
| return false; | |||
| for (;;) | |||
| { | |||
| if (text.isEmpty()) | |||
| return true; | |||
| if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance())) | |||
| return false; | |||
| } | |||
| } | |||
| 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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,775 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
| OF THIS SOFTWARE. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses other parts of JUCE not | |||
| licensed under the ISC terms, commercial licenses are available: visit | |||
| www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_XMLELEMENT_H_INCLUDED | |||
| #define JUCE_XMLELEMENT_H_INCLUDED | |||
| //============================================================================== | |||
| /** 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 <MYTAG> 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()); | |||
| @endcode | |||
| @see XmlDocument | |||
| */ | |||
| class JUCE_API XmlElement | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an XmlElement with this tag name. */ | |||
| explicit XmlElement (const String& tagName); | |||
| /** Creates an XmlElement with this tag name. */ | |||
| explicit XmlElement (const char* tagName); | |||
| /** Creates an XmlElement with this tag name. */ | |||
| explicit XmlElement (const Identifier& tagName); | |||
| /** Creates an XmlElement with this tag name. */ | |||
| explicit XmlElement (StringRef tagName); | |||
| /** Creates an XmlElement with this tag name. */ | |||
| XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd); | |||
| /** Creates a (deep) copy of another element. */ | |||
| XmlElement (const XmlElement&); | |||
| /** Creates a (deep) copy of another element. */ | |||
| XmlElement& operator= (const XmlElement&); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| XmlElement (XmlElement&&) noexcept; | |||
| XmlElement& operator= (XmlElement&&) noexcept; | |||
| #endif | |||
| /** Deleting an XmlElement will also delete all of 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 "<?xml version..etc" line at the start of the | |||
| document | |||
| @param encodingType the character encoding format string to put into the xml | |||
| header | |||
| @param lineWrapLength the line length that will be used before items get placed on | |||
| a new line. This isn't an absolute maximum length, it just | |||
| determines how lists of attributes get broken up | |||
| @see writeToStream, writeToFile | |||
| */ | |||
| String createDocument (StringRef dtdToUse, | |||
| bool allOnOneLine = false, | |||
| bool includeXmlHeader = true, | |||
| StringRef encodingType = "UTF-8", | |||
| int lineWrapLength = 60) const; | |||
| /** Writes the document to a stream as UTF-8. | |||
| @param output the stream to write to | |||
| @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 "<?xml version..etc" line at the start of the | |||
| document | |||
| @param encodingType the character encoding format string to put into the xml | |||
| header | |||
| @param lineWrapLength the line length that will be used before items get placed on | |||
| a new line. This isn't an absolute maximum length, it just | |||
| determines how lists of attributes get broken up | |||
| @see writeToFile, createDocument | |||
| */ | |||
| void writeToStream (OutputStream& output, | |||
| StringRef dtdToUse, | |||
| bool allOnOneLine = false, | |||
| bool includeXmlHeader = true, | |||
| StringRef encodingType = "UTF-8", | |||
| int lineWrapLength = 60) const; | |||
| /** Writes the element to a file as an XML document. | |||
| To improve safety in case something goes wrong while writing the file, this | |||
| will actually write the document to a new temporary file in the same | |||
| directory as the destination file, and if this succeeds, it will rename this | |||
| new file as the destination file (overwriting any existing file that was there). | |||
| @param destinationFile the file to write to. If this already exists, it will be | |||
| overwritten. | |||
| @param dtdToUse the DTD to add to the document | |||
| @param encodingType the character encoding format string to put into the xml | |||
| header | |||
| @param lineWrapLength the line length that will be used before items get placed on | |||
| a new line. This isn't an absolute maximum length, it just | |||
| determines how lists of attributes get broken up | |||
| @returns true if the file is written successfully; false if something goes wrong | |||
| in the process | |||
| @see createDocument | |||
| */ | |||
| bool writeToFile (const File& destinationFile, | |||
| StringRef dtdToUse, | |||
| StringRef encodingType = "UTF-8", | |||
| int lineWrapLength = 60) const; | |||
| //============================================================================== | |||
| /** Returns this element's tag type name. | |||
| E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would return "MOOSE". | |||
| @see hasTagName | |||
| */ | |||
| 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 (StringRef 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 (StringRef possibleTagName) const; | |||
| //============================================================================== | |||
| /** Returns the number of XML attributes this element contains. | |||
| E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would | |||
| return 2. | |||
| */ | |||
| int getNumAttributes() const noexcept; | |||
| /** Returns the name of one of the elements attributes. | |||
| E.g. for an element such as \<MOOSE legs="4" antlers="2">, 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 \<MOOSE legs="4" antlers="2">, 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 (StringRef attributeName) const noexcept; | |||
| /** Returns the value of a named attribute. | |||
| @param attributeName the name of the attribute to look up | |||
| */ | |||
| const String& getStringAttribute (StringRef 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 (StringRef 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 (StringRef attributeName, | |||
| StringRef 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 (StringRef 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 a double (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 (StringRef 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 (StringRef 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 Identifier& 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 Identifier& 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 Identifier& attributeName, double newValue); | |||
| /** Removes a named attribute from the element. | |||
| @param attributeName the name of the attribute to remove | |||
| @see removeAllAttributes | |||
| */ | |||
| void removeAttribute (const Identifier& 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 a nullptr 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 (StringRef 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, getChildByAttribute | |||
| */ | |||
| XmlElement* getChildByName (StringRef tagNameToLookFor) const noexcept; | |||
| /** Returns the first sub-element which has an attribute that matches the given value. | |||
| @param attributeName the name of the attribute to check | |||
| @param attributeValue the target value of the attribute | |||
| @returns the first element with this attribute value, or nullptr if none is found | |||
| @see getChildByName | |||
| */ | |||
| XmlElement* getChildByAttribute (StringRef attributeName, | |||
| StringRef attributeValue) 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. | |||
| Note that due to the XmlElement using a singly-linked-list, prependChildElement() | |||
| is an O(1) operation, but addChildElement() is an O(N) operation - so if | |||
| you're adding large number of elements, you may prefer to do so in reverse order! | |||
| @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 newChildElement 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* newChildElement, | |||
| int indexToInsertAt) noexcept; | |||
| /** Inserts an element at the beginning of 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. | |||
| Note that due to the XmlElement using a singly-linked-list, prependChildElement() | |||
| is an O(1) operation, but addChildElement() is an O(N) operation - so if | |||
| you're adding large number of elements, you may prefer to do so in reverse order! | |||
| @see addChildElement, insertChildElement | |||
| */ | |||
| void prependChildElement (XmlElement* newChildElement) 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 (StringRef 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 (StringRef 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 of this one, looking for an element | |||
| which is the direct parent of the specified element. | |||
| Because elements don't store a pointer to their parent, if you have one | |||
| and need to find its parent, the only way to do so is to exhaustively | |||
| search the whole tree for it. | |||
| If the given child is found somewhere in this element's hierarchy, then | |||
| this method will return its parent. If not, it will return nullptr. | |||
| */ | |||
| XmlElement* findParentElementOf (const XmlElement* childToSearchFor) 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 <class ElementComparator> | |||
| void sortChildElements (ElementComparator& comparator, | |||
| bool retainOrderOfEquivalentItems = false) | |||
| { | |||
| const int num = getNumChildElements(); | |||
| if (num > 1) | |||
| { | |||
| HeapBlock<XmlElement*> 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: | |||
| @code<xyz>hello</xyz>@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. @code<xyz>hello <x>there</x> world</xyz>@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 (StringRef 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); | |||
| /** Checks if a given string is a valid XML name */ | |||
| static bool isValidXmlName (StringRef possibleName) noexcept; | |||
| //============================================================================== | |||
| private: | |||
| struct XmlAttributeNode | |||
| { | |||
| XmlAttributeNode (const XmlAttributeNode&) noexcept; | |||
| XmlAttributeNode (const Identifier&, const String&) noexcept; | |||
| XmlAttributeNode (String::CharPointerType, String::CharPointerType); | |||
| LinkedListPointer<XmlAttributeNode> nextListItem; | |||
| Identifier name; | |||
| String value; | |||
| private: | |||
| XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION; | |||
| }; | |||
| friend class XmlDocument; | |||
| friend class LinkedListPointer<XmlAttributeNode>; | |||
| friend class LinkedListPointer<XmlElement>; | |||
| friend class LinkedListPointer<XmlElement>::Appender; | |||
| friend class NamedValueSet; | |||
| LinkedListPointer<XmlElement> nextListItem; | |||
| LinkedListPointer<XmlElement> firstChildElement; | |||
| LinkedListPointer<XmlAttributeNode> 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; | |||
| XmlAttributeNode* getAttribute (StringRef) const noexcept; | |||
| // Sigh.. L"" or _T("") string literals are problematic in general, and really inappropriate | |||
| // for XML tags. Use a UTF-8 encoded literal instead, or if you're really determined to use | |||
| // UTF-16, cast it to a String and use the other constructor. | |||
| XmlElement (const wchar_t*) JUCE_DELETED_FUNCTION; | |||
| JUCE_LEAK_DETECTOR (XmlElement) | |||
| }; | |||
| #endif // JUCE_XMLELEMENT_H_INCLUDED | |||