diff --git a/.gitignore b/.gitignore index 560634ac9..fd4042f21 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h b/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h index 0c6f9d214..c53722152 100644 --- a/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h +++ b/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h @@ -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); diff --git a/source/modules/juce_audio_graph/containers/juce_LinkedListPointer.h b/source/modules/juce_audio_graph/containers/juce_LinkedListPointer.h new file mode 100644 index 000000000..9581992af --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_LinkedListPointer.h @@ -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 instead). + LinkedListPointer nextListItem; + }; + + LinkedListPointer myList; + myList.append (new MyObject()); + myList.append (new MyObject()); + + int numItems = myList.size(); // returns 2 + MyObject* lastInList = myList.getLast(); + @endcode +*/ +template +class LinkedListPointer +{ +public: + //============================================================================== + /** Creates a null pointer to an empty list. */ + LinkedListPointer() noexcept + : item (nullptr) + { + } + + /** Creates a pointer to a list whose head is the item provided. */ + explicit LinkedListPointer (ObjectType* const headItem) noexcept + : item (headItem) + { + } + + /** Sets this pointer to point to a new list. */ + LinkedListPointer& operator= (ObjectType* const newItem) noexcept + { + item = newItem; + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + LinkedListPointer (LinkedListPointer&& other) noexcept + : item (other.item) + { + other.item = nullptr; + } + + LinkedListPointer& operator= (LinkedListPointer&& other) noexcept + { + jassert (this != &other); // hopefully the compiler should make this situation impossible! + + item = other.item; + other.item = nullptr; + return *this; + } + #endif + + //============================================================================== + /** Returns the item which this pointer points to. */ + inline operator ObjectType*() const noexcept + { + return item; + } + + /** Returns the item which this pointer points to. */ + inline ObjectType* get() const noexcept + { + return item; + } + + /** Returns the last item in the list which this pointer points to. + This will iterate the list and return the last item found. Obviously the speed + of this operation will be proportional to the size of the list. If the list is + empty the return value will be this object. + If you're planning on appending a number of items to your list, it's much more + efficient to use the Appender class than to repeatedly call getLast() to find the end. + */ + LinkedListPointer& getLast() noexcept + { + LinkedListPointer* l = this; + + while (l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns the number of items in the list. + Obviously with a simple linked list, getting the size involves iterating the list, so + this can be a lengthy operation - be careful when using this method in your code. + */ + int size() const noexcept + { + int total = 0; + + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + ++total; + + return total; + } + + /** Returns the item at a given index in the list. + Since the only way to find an item is to iterate the list, this operation can obviously + be slow, depending on its size, so you should be careful when using this in algorithms. + */ + LinkedListPointer& operator[] (int index) noexcept + { + LinkedListPointer* l = this; + + while (--index >= 0 && l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns the item at a given index in the list. + Since the only way to find an item is to iterate the list, this operation can obviously + be slow, depending on its size, so you should be careful when using this in algorithms. + */ + const LinkedListPointer& operator[] (int index) const noexcept + { + const LinkedListPointer* l = this; + + while (--index >= 0 && l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns true if the list contains the given item. */ + bool contains (const ObjectType* const itemToLookFor) const noexcept + { + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + if (itemToLookFor == i) + return true; + + return false; + } + + //============================================================================== + /** Inserts an item into the list, placing it before the item that this pointer + currently points to. + */ + void insertNext (ObjectType* const newItem) + { + jassert (newItem != nullptr); + jassert (newItem->nextListItem == nullptr); + newItem->nextListItem = item; + item = newItem; + } + + /** Inserts an item at a numeric index in the list. + Obviously this will involve iterating the list to find the item at the given index, + so be careful about the impact this may have on execution time. + */ + void insertAtIndex (int index, ObjectType* newItem) + { + jassert (newItem != nullptr); + LinkedListPointer* l = this; + + while (index != 0 && l->item != nullptr) + { + l = &(l->item->nextListItem); + --index; + } + + l->insertNext (newItem); + } + + /** Replaces the object that this pointer points to, appending the rest of the list to + the new object, and returning the old one. + */ + ObjectType* replaceNext (ObjectType* const newItem) noexcept + { + jassert (newItem != nullptr); + jassert (newItem->nextListItem == nullptr); + + ObjectType* const oldItem = item; + item = newItem; + item->nextListItem = oldItem->nextListItem.item; + oldItem->nextListItem.item = nullptr; + return oldItem; + } + + /** Adds an item to the end of the list. + + This operation involves iterating the whole list, so can be slow - if you need to + append a number of items to your list, it's much more efficient to use the Appender + class than to repeatedly call append(). + */ + void append (ObjectType* const newItem) + { + getLast().item = newItem; + } + + /** Creates copies of all the items in another list and adds them to this one. + This will use the ObjectType's copy constructor to try to create copies of each + item in the other list, and appends them to this list. + */ + void addCopyOfList (const LinkedListPointer& other) + { + LinkedListPointer* insertPoint = this; + + for (ObjectType* i = other.item; i != nullptr; i = i->nextListItem) + { + insertPoint->insertNext (new ObjectType (*i)); + insertPoint = &(insertPoint->item->nextListItem); + } + } + + /** Removes the head item from the list. + This won't delete the object that is removed, but returns it, so the caller can + delete it if necessary. + */ + ObjectType* removeNext() noexcept + { + ObjectType* const oldItem = item; + + if (oldItem != nullptr) + { + item = oldItem->nextListItem; + oldItem->nextListItem.item = nullptr; + } + + return oldItem; + } + + /** Removes a specific item from the list. + Note that this will not delete the item, it simply unlinks it from the list. + */ + void remove (ObjectType* const itemToRemove) + { + if (LinkedListPointer* const l = findPointerTo (itemToRemove)) + l->removeNext(); + } + + /** Iterates the list, calling the delete operator on all of its elements and + leaving this pointer empty. + */ + void deleteAll() + { + while (item != nullptr) + { + ObjectType* const oldItem = item; + item = oldItem->nextListItem; + delete oldItem; + } + } + + /** Finds a pointer to a given item. + If the item is found in the list, this returns the pointer that points to it. If + the item isn't found, this returns null. + */ + LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept + { + LinkedListPointer* l = this; + + while (l->item != nullptr) + { + if (l->item == itemToLookFor) + return l; + + l = &(l->item->nextListItem); + } + + return nullptr; + } + + /** Copies the items in the list to an array. + The destArray must contain enough elements to hold the entire list - no checks are + made for this! + */ + void copyToArray (ObjectType** destArray) const noexcept + { + jassert (destArray != nullptr); + + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + *destArray++ = i; + } + + /** Swaps this pointer with another one */ + void swapWith (LinkedListPointer& other) noexcept + { + std::swap (item, other.item); + } + + //============================================================================== + /** + Allows efficient repeated insertions into a list. + + You can create an Appender object which points to the last element in your + list, and then repeatedly call Appender::append() to add items to the end + of the list in O(1) time. + */ + class Appender + { + public: + /** Creates an appender which will add items to the given list. + */ + Appender (LinkedListPointer& endOfListPointer) noexcept + : endOfList (&endOfListPointer) + { + // This can only be used to add to the end of a list. + jassert (endOfListPointer.item == nullptr); + } + + /** Appends an item to the list. */ + void append (ObjectType* const newItem) noexcept + { + *endOfList = newItem; + endOfList = &(newItem->nextListItem); + } + + private: + LinkedListPointer* endOfList; + + JUCE_DECLARE_NON_COPYABLE (Appender) + }; + +private: + //============================================================================== + ObjectType* item; + + JUCE_DECLARE_NON_COPYABLE (LinkedListPointer) +}; + + +#endif // JUCE_LINKEDLISTPOINTER_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_ReferenceCountedArray.h b/source/modules/juce_audio_graph/containers/juce_ReferenceCountedArray.h new file mode 100644 index 000000000..2ff211ab1 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_ReferenceCountedArray.h @@ -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 ReferenceCountedArray +{ +public: + typedef ReferenceCountedObjectPtr ObjectClassPtr; + + //============================================================================== + /** Creates an empty array. + @see ReferenceCountedObject, Array, OwnedArray + */ + ReferenceCountedArray() noexcept + : numUsed (0) + { + } + + /** Creates a copy of another array */ + ReferenceCountedArray (const ReferenceCountedArray& other) noexcept + { + 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 + ReferenceCountedArray (const ReferenceCountedArray& 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 + ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept + { + ReferenceCountedArray otherCopy (other); + swapWith (otherCopy); + return *this; + } + + /** Destructor. + Any objects in the array will be released, and may be deleted if not referenced from elsewhere. + */ + ~ReferenceCountedArray() + { + 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 (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& 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 + 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 + 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 + 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 + 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& other) const noexcept + { + return ! operator== (other); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + + @see sortArray + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) const noexcept + { + 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 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 diff --git a/source/modules/juce_audio_graph/containers/juce_SortedSet.h b/source/modules/juce_audio_graph/containers/juce_SortedSet.h new file mode 100644 index 000000000..bdca14917 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_SortedSet.h @@ -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 SortedSet +{ +public: + //============================================================================== + /** Creates an empty set. */ + SortedSet() noexcept + { + } + + /** Creates a copy of another set. + @param other the set to copy + */ + SortedSet (const SortedSet& other) + : data (other.data) + { + } + + /** Destructor. */ + ~SortedSet() noexcept + { + } + + /** Copies another set over this one. + @param other the set to copy + */ + SortedSet& operator= (const SortedSet& other) noexcept + { + data = other.data; + return *this; + } + + //============================================================================== + /** Compares this set to another one. + Two sets are considered equal if they both contain the same set of elements. + @param other the other set to compare with + */ + bool operator== (const SortedSet& other) const noexcept + { + return data == other.data; + } + + /** Compares this set to another one. + Two sets are considered equal if they both contain the same set of elements. + @param other the other set to compare with + */ + bool operator!= (const SortedSet& other) const noexcept + { + return ! operator== (other); + } + + //============================================================================== + /** Removes all elements from the set. + + This will remove all the elements, and free any storage that the set is + using. To clear it without freeing the storage, use the clearQuick() + method instead. + + @see clearQuick + */ + void clear() noexcept + { + data.clear(); + } + + /** Removes all elements from the set without freeing the array's allocated storage. + @see clear + */ + void clearQuick() noexcept + { + data.clearQuick(); + } + + //============================================================================== + /** Returns the current number of elements in the set. */ + inline int size() const noexcept + { + return data.size(); + } + + /** Returns 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 + 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 + 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 + 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 + 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 data; +}; + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_SORTEDSET_H_INCLUDED diff --git a/source/modules/juce_audio_graph/files/juce_File.cpp b/source/modules/juce_audio_graph/files/juce_File.cpp new file mode 100644 index 000000000..9b99a95c3 --- /dev/null +++ b/source/modules/juce_audio_graph/files/juce_File.cpp @@ -0,0 +1,1210 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +File::File (const String& fullPathName) + : fullPath (parseAbsolutePath (fullPathName)) +{ +} + +File File::createFileWithoutCheckingPath (const String& path) noexcept +{ + File f; + f.fullPath = path; + return f; +} + +File::File (const File& other) + : fullPath (other.fullPath) +{ +} + +File& File::operator= (const String& newPath) +{ + fullPath = parseAbsolutePath (newPath); + return *this; +} + +File& File::operator= (const File& other) +{ + fullPath = other.fullPath; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +File::File (File&& other) noexcept + : fullPath (static_cast (other.fullPath)) +{ +} + +File& File::operator= (File&& other) noexcept +{ + fullPath = static_cast (other.fullPath); + return *this; +} +#endif + +#if JUCE_ALLOW_STATIC_NULL_VARIABLES +const File File::nonexistent; +#endif + +//============================================================================== +static String removeEllipsis (const String& path) +{ + // This will quickly find both /../ and /./ at the expense of a minor + // false-positive performance hit when path elements end in a dot. + #if JUCE_WINDOWS + if (path.contains (".\\")) + #else + if (path.contains ("./")) + #endif + { + StringArray toks; + toks.addTokens (path, File::separatorString, StringRef()); + bool anythingChanged = false; + + for (int i = 1; i < toks.size(); ++i) + { + const String& t = toks[i]; + + if (t == ".." && toks[i - 1] != "..") + { + anythingChanged = true; + toks.removeRange (i - 1, 2); + i = jmax (0, i - 2); + } + else if (t == ".") + { + anythingChanged = true; + toks.remove (i--); + } + } + + if (anythingChanged) + return toks.joinIntoString (File::separatorString); + } + + return path; +} + +String File::parseAbsolutePath (const String& p) +{ + if (p.isEmpty()) + return String(); + +#if JUCE_WINDOWS + // Windows.. + String path (removeEllipsis (p.replaceCharacter ('/', '\\'))); + + if (path.startsWithChar (separator)) + { + if (path[1] != separator) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; + } + } + else if (! path.containsChar (':')) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); + } +#else + // Mac or Linux.. + + // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here + // to catch anyone who's trying to run code that was written on Windows with hard-coded path names. + // If that's why you've ended up here, use File::getChildFile() to build your paths instead. + jassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\'))); + + String path (removeEllipsis (p)); + + if (path.startsWithChar ('~')) + { + if (path[1] == separator || path[1] == 0) + { + // expand a name of the form "~/abc" + path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName() + + path.substring (1); + } + else + { + // expand a name of type "~dave/abc" + const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false)); + + if (struct passwd* const pw = getpwnam (userName.toUTF8())) + path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false); + } + } + else if (! path.startsWithChar (separator)) + { + #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS + if (! (path.startsWith ("./") || path.startsWith ("../"))) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + #if JUCE_LOG_ASSERTIONS + Logger::writeToLog ("Illegal absolute path: " + path); + #endif + } + #endif + + return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); + } +#endif + + while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string. + path = path.dropLastCharacters (1); + + return path; +} + +String File::addTrailingSeparator (const String& path) +{ + return path.endsWithChar (separator) ? path + : path + separator; +} + +//============================================================================== +#if JUCE_LINUX + #define NAMES_ARE_CASE_SENSITIVE 1 +#endif + +bool File::areFileNamesCaseSensitive() +{ + #if NAMES_ARE_CASE_SENSITIVE + return true; + #else + return false; + #endif +} + +static int compareFilenames (const String& name1, const String& name2) noexcept +{ + #if NAMES_ARE_CASE_SENSITIVE + return name1.compare (name2); + #else + return name1.compareIgnoreCase (name2); + #endif +} + +bool File::operator== (const File& other) const { return compareFilenames (fullPath, other.fullPath) == 0; } +bool File::operator!= (const File& other) const { return compareFilenames (fullPath, other.fullPath) != 0; } +bool File::operator< (const File& other) const { return compareFilenames (fullPath, other.fullPath) < 0; } +bool File::operator> (const File& other) const { return compareFilenames (fullPath, other.fullPath) > 0; } + +//============================================================================== +bool File::setReadOnly (const bool shouldBeReadOnly, + const bool applyRecursively) const +{ + bool worked = true; + + if (applyRecursively && isDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (int i = subFiles.size(); --i >= 0;) + worked = subFiles.getReference(i).setReadOnly (shouldBeReadOnly, true) && worked; + } + + return setFileReadOnlyInternal (shouldBeReadOnly) && worked; +} + +bool File::setExecutePermission (bool shouldBeExecutable) const +{ + return setFileExecutableInternal (shouldBeExecutable); +} + +bool File::deleteRecursively() const +{ + bool worked = true; + + if (isDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (int i = subFiles.size(); --i >= 0;) + worked = subFiles.getReference(i).deleteRecursively() && worked; + } + + return deleteFile() && worked; +} + +bool File::moveFileTo (const File& newFile) const +{ + if (newFile.fullPath == fullPath) + return true; + + if (! exists()) + return false; + + #if ! NAMES_ARE_CASE_SENSITIVE + if (*this != newFile) + #endif + if (! newFile.deleteFile()) + return false; + + return moveInternal (newFile); +} + +bool File::copyFileTo (const File& newFile) const +{ + return (*this == newFile) + || (exists() && newFile.deleteFile() && copyInternal (newFile)); +} + +bool File::replaceFileIn (const File& newFile) const +{ + if (newFile.fullPath == fullPath) + return true; + + if (! newFile.exists()) + return moveFileTo (newFile); + + if (! replaceInternal (newFile)) + return false; + + deleteFile(); + return true; +} + +bool File::copyDirectoryTo (const File& newDirectory) const +{ + if (isDirectory() && newDirectory.createDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFiles, false); + + for (int i = 0; i < subFiles.size(); ++i) + if (! subFiles.getReference(i).copyFileTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) + return false; + + subFiles.clear(); + findChildFiles (subFiles, File::findDirectories, false); + + for (int i = 0; i < subFiles.size(); ++i) + if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) + return false; + + return true; + } + + return false; +} + +//============================================================================== +String File::getPathUpToLastSlash() const +{ + const int lastSlash = fullPath.lastIndexOfChar (separator); + + if (lastSlash > 0) + return fullPath.substring (0, lastSlash); + + if (lastSlash == 0) + return separatorString; + + return fullPath; +} + +File File::getParentDirectory() const +{ + File f; + f.fullPath = getPathUpToLastSlash(); + return f; +} + +//============================================================================== +String File::getFileName() const +{ + return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1); +} + +String File::getFileNameWithoutExtension() const +{ + const int lastSlash = fullPath.lastIndexOfChar (separator) + 1; + const int lastDot = fullPath.lastIndexOfChar ('.'); + + if (lastDot > lastSlash) + return fullPath.substring (lastSlash, lastDot); + + return fullPath.substring (lastSlash); +} + +bool File::isAChildOf (const File& potentialParent) const +{ + if (potentialParent.fullPath.isEmpty()) + return false; + + const String ourPath (getPathUpToLastSlash()); + + if (compareFilenames (potentialParent.fullPath, ourPath) == 0) + return true; + + if (potentialParent.fullPath.length() >= ourPath.length()) + return false; + + return getParentDirectory().isAChildOf (potentialParent); +} + +int File::hashCode() const { return fullPath.hashCode(); } +int64 File::hashCode64() const { return fullPath.hashCode64(); } + +//============================================================================== +bool File::isAbsolutePath (StringRef path) +{ + const juce_wchar firstChar = *(path.text); + + return firstChar == separator + #if JUCE_WINDOWS + || (firstChar != 0 && path.text[1] == ':'); + #else + || firstChar == '~'; + #endif +} + +File File::getChildFile (StringRef relativePath) const +{ + String::CharPointerType r = relativePath.text; + + if (isAbsolutePath (r)) + return File (String (r)); + + #if JUCE_WINDOWS + if (r.indexOf ((juce_wchar) '/') >= 0) + return getChildFile (String (r).replaceCharacter ('/', '\\')); + #endif + + String path (fullPath); + + while (*r == '.') + { + String::CharPointerType lastPos = r; + const juce_wchar secondChar = *++r; + + if (secondChar == '.') // remove "../" + { + const juce_wchar thirdChar = *++r; + + if (thirdChar == separator || thirdChar == 0) + { + const int lastSlash = path.lastIndexOfChar (separator); + if (lastSlash >= 0) + path = path.substring (0, lastSlash); + + while (*r == separator) // ignore duplicate slashes + ++r; + } + else + { + r = lastPos; + break; + } + } + else if (secondChar == separator || secondChar == 0) // remove "./" + { + while (*r == separator) // ignore duplicate slashes + ++r; + } + else + { + r = lastPos; + break; + } + } + + path = addTrailingSeparator (path); + path.appendCharPointer (r); + return File (path); +} + +File File::getSiblingFile (StringRef fileName) const +{ + return getParentDirectory().getChildFile (fileName); +} + +//============================================================================== +String File::descriptionOfSizeInBytes (const int64 bytes) +{ + const char* suffix; + double divisor = 0; + + if (bytes == 1) { suffix = " byte"; } + else if (bytes < 1024) { suffix = " bytes"; } + else if (bytes < 1024 * 1024) { suffix = " KB"; divisor = 1024.0; } + else if (bytes < 1024 * 1024 * 1024) { suffix = " MB"; divisor = 1024.0 * 1024.0; } + else { suffix = " GB"; divisor = 1024.0 * 1024.0 * 1024.0; } + + return (divisor > 0 ? String (bytes / divisor, 1) : String (bytes)) + suffix; +} + +//============================================================================== +Result File::create() const +{ + if (exists()) + return Result::ok(); + + const File parentDir (getParentDirectory()); + + if (parentDir == *this) + return Result::fail ("Cannot create parent directory"); + + Result r (parentDir.createDirectory()); + + if (r.wasOk()) + { + FileOutputStream fo (*this, 8); + r = fo.getStatus(); + } + + return r; +} + +Result File::createDirectory() const +{ + if (isDirectory()) + return Result::ok(); + + const File parentDir (getParentDirectory()); + + if (parentDir == *this) + return Result::fail ("Cannot create parent directory"); + + Result r (parentDir.createDirectory()); + + if (r.wasOk()) + r = createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString)); + + return r; +} + +//============================================================================== +Time File::getLastModificationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); } +Time File::getLastAccessTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); } +Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); } + +bool File::setLastModificationTime (Time t) const { return setFileTimesInternal (t.toMilliseconds(), 0, 0); } +bool File::setLastAccessTime (Time t) const { return setFileTimesInternal (0, t.toMilliseconds(), 0); } +bool File::setCreationTime (Time t) const { return setFileTimesInternal (0, 0, t.toMilliseconds()); } + +//============================================================================== +bool File::loadFileAsData (MemoryBlock& destBlock) const +{ + if (! existsAsFile()) + return false; + + FileInputStream in (*this); + return in.openedOk() && getSize() == (int64) in.readIntoMemoryBlock (destBlock); +} + +String File::loadFileAsString() const +{ + if (! existsAsFile()) + return String(); + + FileInputStream in (*this); + return in.openedOk() ? in.readEntireStreamAsString() + : String(); +} + +void File::readLines (StringArray& destLines) const +{ + destLines.addLines (loadFileAsString()); +} + +//============================================================================== +int File::findChildFiles (Array& results, + const int whatToLookFor, + const bool searchRecursively, + const String& wildCardPattern) const +{ + int total = 0; + + for (DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); di.next();) + { + results.add (di.getFile()); + ++total; + } + + return total; +} + +int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const +{ + int total = 0; + + for (DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor); di.next();) + ++total; + + return total; +} + +bool File::containsSubDirectories() const +{ + if (! isDirectory()) + return false; + + DirectoryIterator di (*this, false, "*", findDirectories); + return di.next(); +} + +//============================================================================== +File File::getNonexistentChildFile (const String& suggestedPrefix, + const String& suffix, + bool putNumbersInBrackets) const +{ + File f (getChildFile (suggestedPrefix + suffix)); + + if (f.exists()) + { + int number = 1; + String prefix (suggestedPrefix); + + // remove any bracketed numbers that may already be on the end.. + if (prefix.trim().endsWithChar (')')) + { + putNumbersInBrackets = true; + + const int openBracks = prefix.lastIndexOfChar ('('); + const int closeBracks = prefix.lastIndexOfChar (')'); + + if (openBracks > 0 + && closeBracks > openBracks + && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789")) + { + number = prefix.substring (openBracks + 1, closeBracks).getIntValue(); + prefix = prefix.substring (0, openBracks); + } + } + + do + { + String newName (prefix); + + if (putNumbersInBrackets) + { + newName << '(' << ++number << ')'; + } + else + { + if (CharacterFunctions::isDigit (prefix.getLastCharacter())) + newName << '_'; // pad with an underscore if the name already ends in a digit + + newName << ++number; + } + + f = getChildFile (newName + suffix); + + } while (f.exists()); + } + + return f; +} + +File File::getNonexistentSibling (const bool putNumbersInBrackets) const +{ + if (! exists()) + return *this; + + return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(), + getFileExtension(), + putNumbersInBrackets); +} + +//============================================================================== +String File::getFileExtension() const +{ + const int indexOfDot = fullPath.lastIndexOfChar ('.'); + + if (indexOfDot > fullPath.lastIndexOfChar (separator)) + return fullPath.substring (indexOfDot); + + return String(); +} + +bool File::hasFileExtension (StringRef possibleSuffix) const +{ + if (possibleSuffix.isEmpty()) + return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator); + + const int semicolon = possibleSuffix.text.indexOf ((juce_wchar) ';'); + + if (semicolon >= 0) + return hasFileExtension (String (possibleSuffix.text).substring (0, semicolon).trimEnd()) + || hasFileExtension ((possibleSuffix.text + (semicolon + 1)).findEndOfWhitespace()); + + if (fullPath.endsWithIgnoreCase (possibleSuffix)) + { + if (possibleSuffix.text[0] == '.') + return true; + + const int dotPos = fullPath.length() - possibleSuffix.length() - 1; + + if (dotPos >= 0) + return fullPath [dotPos] == '.'; + } + + return false; +} + +File File::withFileExtension (StringRef newExtension) const +{ + if (fullPath.isEmpty()) + return File(); + + String filePart (getFileName()); + + const int i = filePart.lastIndexOfChar ('.'); + if (i >= 0) + filePart = filePart.substring (0, i); + + if (newExtension.isNotEmpty() && newExtension.text[0] != '.') + filePart << '.'; + + return getSiblingFile (filePart + newExtension); +} + +//============================================================================== +bool File::startAsProcess (const String& parameters) const +{ + return exists() && Process::openDocument (fullPath, parameters); +} + +//============================================================================== +FileInputStream* File::createInputStream() const +{ + ScopedPointer fin (new FileInputStream (*this)); + + if (fin->openedOk()) + return fin.release(); + + return nullptr; +} + +FileOutputStream* File::createOutputStream (const size_t bufferSize) const +{ + ScopedPointer out (new FileOutputStream (*this, bufferSize)); + + return out->failedToOpen() ? nullptr + : out.release(); +} + +//============================================================================== +bool File::appendData (const void* const dataToAppend, + const size_t numberOfBytes) const +{ + jassert (((ssize_t) numberOfBytes) >= 0); + + if (numberOfBytes == 0) + return true; + + FileOutputStream out (*this, 8192); + return out.openedOk() && out.write (dataToAppend, numberOfBytes); +} + +bool File::replaceWithData (const void* const dataToWrite, + const size_t numberOfBytes) const +{ + if (numberOfBytes == 0) + return deleteFile(); + + TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); + tempFile.getFile().appendData (dataToWrite, numberOfBytes); + return tempFile.overwriteTargetFileWithTemporary(); +} + +bool File::appendText (const String& text, + const bool asUnicode, + const bool writeUnicodeHeaderBytes) const +{ + FileOutputStream out (*this); + + if (out.failedToOpen()) + return false; + + out.writeText (text, asUnicode, writeUnicodeHeaderBytes); + return true; +} + +bool File::replaceWithText (const String& textToWrite, + const bool asUnicode, + const bool writeUnicodeHeaderBytes) const +{ + TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); + tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes); + return tempFile.overwriteTargetFileWithTemporary(); +} + +bool File::hasIdenticalContentTo (const File& other) const +{ + if (other == *this) + return true; + + if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile()) + { + FileInputStream in1 (*this), in2 (other); + + if (in1.openedOk() && in2.openedOk()) + { + const int bufferSize = 4096; + HeapBlock buffer1 (bufferSize), buffer2 (bufferSize); + + for (;;) + { + const int num1 = in1.read (buffer1, bufferSize); + const int num2 = in2.read (buffer2, bufferSize); + + if (num1 != num2) + break; + + if (num1 <= 0) + return true; + + if (memcmp (buffer1, buffer2, (size_t) num1) != 0) + break; + } + } + } + + return false; +} + +//============================================================================== +String File::createLegalPathName (const String& original) +{ + String s (original); + String start; + + if (s.isNotEmpty() && s[1] == ':') + { + start = s.substring (0, 2); + s = s.substring (2); + } + + return start + s.removeCharacters ("\"#@,;:<>*^|?") + .substring (0, 1024); +} + +String File::createLegalFileName (const String& original) +{ + String s (original.removeCharacters ("\"#@,;:<>*^|?\\/")); + + const int maxLength = 128; // only the length of the filename, not the whole path + const int len = s.length(); + + if (len > maxLength) + { + const int lastDot = s.lastIndexOfChar ('.'); + + if (lastDot > jmax (0, len - 12)) + { + s = s.substring (0, maxLength - (len - lastDot)) + + s.substring (lastDot); + } + else + { + s = s.substring (0, maxLength); + } + } + + return s; +} + +//============================================================================== +static int countNumberOfSeparators (String::CharPointerType s) +{ + int num = 0; + + for (;;) + { + const juce_wchar c = s.getAndAdvance(); + + if (c == 0) + break; + + if (c == File::separator) + ++num; + } + + return num; +} + +String File::getRelativePathFrom (const File& dir) const +{ + String thisPath (fullPath); + + while (thisPath.endsWithChar (separator)) + thisPath = thisPath.dropLastCharacters (1); + + String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName() + : dir.fullPath)); + + int commonBitLength = 0; + String::CharPointerType thisPathAfterCommon (thisPath.getCharPointer()); + String::CharPointerType dirPathAfterCommon (dirPath.getCharPointer()); + + { + String::CharPointerType thisPathIter (thisPath.getCharPointer()); + String::CharPointerType dirPathIter (dirPath.getCharPointer()); + + for (int i = 0;;) + { + const juce_wchar c1 = thisPathIter.getAndAdvance(); + const juce_wchar c2 = dirPathIter.getAndAdvance(); + + #if NAMES_ARE_CASE_SENSITIVE + if (c1 != c2 + #else + if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2)) + #endif + || c1 == 0) + break; + + ++i; + + if (c1 == separator) + { + thisPathAfterCommon = thisPathIter; + dirPathAfterCommon = dirPathIter; + commonBitLength = i; + } + } + } + + // if the only common bit is the root, then just return the full path.. + if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == separator)) + return fullPath; + + const int numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon); + + if (numUpDirectoriesNeeded == 0) + return thisPathAfterCommon; + + #if JUCE_WINDOWS + String s (String::repeatedString ("..\\", numUpDirectoriesNeeded)); + #else + String s (String::repeatedString ("../", numUpDirectoriesNeeded)); + #endif + s.appendCharPointer (thisPathAfterCommon); + return s; +} + +//============================================================================== +File File::createTempFile (StringRef fileNameEnding) +{ + const File tempFile (getSpecialLocation (tempDirectory) + .getChildFile ("temp_" + String::toHexString (Random::getSystemRandom().nextInt())) + .withFileExtension (fileNameEnding)); + + if (tempFile.exists()) + return createTempFile (fileNameEnding); + + return tempFile; +} + +bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const +{ + if (linkFileToCreate.exists()) + { + if (! linkFileToCreate.isSymbolicLink()) + { + // user has specified an existing file / directory as the link + // this is bad! the user could end up unintentionally destroying data + jassertfalse; + return false; + } + + if (overwriteExisting) + linkFileToCreate.deleteFile(); + } + + #if JUCE_MAC || JUCE_LINUX + // one common reason for getting an error here is that the file already exists + if (symlink (fullPath.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1) + { + jassertfalse; + return false; + } + + return true; + #elif JUCE_MSVC + return CreateSymbolicLink (linkFileToCreate.getFullPathName().toWideCharPointer(), + fullPath.toWideCharPointer(), + isDirectory() ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != FALSE; + #else + jassertfalse; // symbolic links not supported on this platform! + return false; + #endif +} + +//============================================================================== +MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode, bool exclusive) + : address (nullptr), range (0, file.getSize()), fileHandle (0) +{ + openInternal (file, mode, exclusive); +} + +MemoryMappedFile::MemoryMappedFile (const File& file, const Range& fileRange, AccessMode mode, bool exclusive) + : address (nullptr), range (fileRange.getIntersectionWith (Range (0, file.getSize()))), fileHandle (0) +{ + openInternal (file, mode, exclusive); +} + + +//============================================================================== +#if JUCE_UNIT_TESTS + +class FileTests : public UnitTest +{ +public: + FileTests() : UnitTest ("Files") {} + + void runTest() override + { + beginTest ("Reading"); + + const File home (File::getSpecialLocation (File::userHomeDirectory)); + const File temp (File::getSpecialLocation (File::tempDirectory)); + + expect (! File().exists()); + expect (! File().existsAsFile()); + expect (! File().isDirectory()); + #if ! JUCE_WINDOWS + expect (File("/").isDirectory()); + #endif + expect (home.isDirectory()); + expect (home.exists()); + expect (! home.existsAsFile()); + expect (File::getSpecialLocation (File::userDocumentsDirectory).isDirectory()); + expect (File::getSpecialLocation (File::userApplicationDataDirectory).isDirectory()); + expect (File::getSpecialLocation (File::currentExecutableFile).exists()); + expect (File::getSpecialLocation (File::currentApplicationFile).exists()); + expect (File::getSpecialLocation (File::invokedExecutableFile).exists()); + expect (home.getVolumeTotalSize() > 1024 * 1024); + expect (home.getBytesFreeOnVolume() > 0); + expect (! home.isHidden()); + expect (home.isOnHardDisk()); + expect (! home.isOnCDRomDrive()); + expect (File::getCurrentWorkingDirectory().exists()); + expect (home.setAsCurrentWorkingDirectory()); + expect (File::getCurrentWorkingDirectory() == home); + + { + Array roots; + File::findFileSystemRoots (roots); + expect (roots.size() > 0); + + int numRootsExisting = 0; + for (int i = 0; i < roots.size(); ++i) + if (roots[i].exists()) + ++numRootsExisting; + + // (on windows, some of the drives may not contain media, so as long as at least one is ok..) + expect (numRootsExisting > 0); + } + + beginTest ("Writing"); + + File demoFolder (temp.getChildFile ("Juce UnitTests Temp Folder.folder")); + expect (demoFolder.deleteRecursively()); + expect (demoFolder.createDirectory()); + expect (demoFolder.isDirectory()); + expect (demoFolder.getParentDirectory() == temp); + expect (temp.isDirectory()); + + { + Array files; + temp.findChildFiles (files, File::findFilesAndDirectories, false, "*"); + expect (files.contains (demoFolder)); + } + + { + Array files; + temp.findChildFiles (files, File::findDirectories, true, "*.folder"); + expect (files.contains (demoFolder)); + } + + File tempFile (demoFolder.getNonexistentChildFile ("test", ".txt", false)); + + expect (tempFile.getFileExtension() == ".txt"); + expect (tempFile.hasFileExtension (".txt")); + expect (tempFile.hasFileExtension ("txt")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension (".xyz")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension ("abc;xyz;foo")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension ("xyz;foo")); + expect (! tempFile.withFileExtension ("h").hasFileExtension ("bar;foo;xx")); + expect (tempFile.getSiblingFile ("foo").isAChildOf (temp)); + expect (tempFile.hasWriteAccess()); + + expect (home.getChildFile (".") == home); + expect (home.getChildFile ("..") == home.getParentDirectory()); + expect (home.getChildFile (".xyz").getFileName() == ".xyz"); + expect (home.getChildFile ("..xyz").getFileName() == "..xyz"); + expect (home.getChildFile ("...xyz").getFileName() == "...xyz"); + expect (home.getChildFile ("./xyz") == home.getChildFile ("xyz")); + expect (home.getChildFile ("././xyz") == home.getChildFile ("xyz")); + expect (home.getChildFile ("../xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile (".././xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile (".././xyz/./abc") == home.getParentDirectory().getChildFile ("xyz/abc")); + expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4")); + + { + FileOutputStream fo (tempFile); + fo.write ("0123456789", 10); + } + + expect (tempFile.exists()); + expect (tempFile.getSize() == 10); + expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); + expectEquals (tempFile.loadFileAsString(), String ("0123456789")); + expect (! demoFolder.containsSubDirectories()); + + expectEquals (tempFile.getRelativePathFrom (demoFolder.getParentDirectory()), demoFolder.getFileName() + File::separatorString + tempFile.getFileName()); + expectEquals (demoFolder.getParentDirectory().getRelativePathFrom (tempFile), ".." + File::separatorString + ".." + File::separatorString + demoFolder.getParentDirectory().getFileName()); + + expect (demoFolder.getNumberOfChildFiles (File::findFiles) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 0); + demoFolder.getNonexistentChildFile ("tempFolder", "", false).createDirectory(); + expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 2); + expect (demoFolder.containsSubDirectories()); + + expect (tempFile.hasWriteAccess()); + tempFile.setReadOnly (true); + expect (! tempFile.hasWriteAccess()); + tempFile.setReadOnly (false); + expect (tempFile.hasWriteAccess()); + + Time t (Time::getCurrentTime()); + tempFile.setLastModificationTime (t); + Time t2 = tempFile.getLastModificationTime(); + expect (std::abs ((int) (t2.toMilliseconds() - t.toMilliseconds())) <= 1000); + + { + MemoryBlock mb; + tempFile.loadFileAsData (mb); + expect (mb.getSize() == 10); + expect (mb[0] == '0'); + } + + { + expect (tempFile.getSize() == 10); + FileOutputStream fo (tempFile); + expect (fo.openedOk()); + + expect (fo.setPosition (7)); + expect (fo.truncate().wasOk()); + expect (tempFile.getSize() == 7); + fo.write ("789", 3); + fo.flush(); + expect (tempFile.getSize() == 10); + } + + beginTest ("Memory-mapped files"); + + { + MemoryMappedFile mmf (tempFile, MemoryMappedFile::readOnly); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + expect (memcmp (mmf.getData(), "0123456789", 10) == 0); + } + + { + const File tempFile2 (tempFile.getNonexistentSibling (false)); + expect (tempFile2.create()); + expect (tempFile2.appendData ("xxxxxxxxxx", 10)); + + { + MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + memcpy (mmf.getData(), "abcdefghij", 10); + } + + { + MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + expect (memcmp (mmf.getData(), "abcdefghij", 10) == 0); + } + + expect (tempFile2.deleteFile()); + } + + beginTest ("More writing"); + + expect (tempFile.appendData ("abcdefghij", 10)); + expect (tempFile.getSize() == 20); + expect (tempFile.replaceWithData ("abcdefghij", 10)); + expect (tempFile.getSize() == 10); + + File tempFile2 (tempFile.getNonexistentSibling (false)); + expect (tempFile.copyFileTo (tempFile2)); + expect (tempFile2.exists()); + expect (tempFile2.hasIdenticalContentTo (tempFile)); + expect (tempFile.deleteFile()); + expect (! tempFile.exists()); + expect (tempFile2.moveFileTo (tempFile)); + expect (tempFile.exists()); + expect (! tempFile2.exists()); + + expect (demoFolder.deleteRecursively()); + expect (! demoFolder.exists()); + } +}; + +static FileTests fileUnitTests; + +#endif diff --git a/source/modules/juce_audio_graph/files/juce_File.h b/source/modules/juce_audio_graph/files/juce_File.h new file mode 100644 index 000000000..762a09e69 --- /dev/null +++ b/source/modules/juce_audio_graph/files/juce_File.h @@ -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& 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& 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 diff --git a/source/modules/juce_audio_graph/files/juce_TemporaryFile.cpp b/source/modules/juce_audio_graph/files/juce_TemporaryFile.cpp new file mode 100644 index 000000000..41ea4dcef --- /dev/null +++ b/source/modules/juce_audio_graph/files/juce_TemporaryFile.cpp @@ -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; +} diff --git a/source/modules/juce_audio_graph/files/juce_TemporaryFile.h b/source/modules/juce_audio_graph/files/juce_TemporaryFile.h new file mode 100644 index 000000000..408b8bfe2 --- /dev/null +++ b/source/modules/juce_audio_graph/files/juce_TemporaryFile.h @@ -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 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 diff --git a/source/modules/juce_audio_graph/juce_audio_graph.cpp b/source/modules/juce_audio_graph/juce_audio_graph.cpp index 0c2e7ce24..a4b585784 100644 --- a/source/modules/juce_audio_graph/juce_audio_graph.cpp +++ b/source/modules/juce_audio_graph/juce_audio_graph.cpp @@ -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 - #include #include #include @@ -43,33 +36,13 @@ namespace juce2 // #include "streams/juce_InputStream.cpp" // #include "streams/juce_OutputStream.cpp" -// static inline bool arrayContainsPlugin (const OwnedArray& 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" } diff --git a/source/modules/juce_audio_graph/juce_audio_graph.h b/source/modules/juce_audio_graph/juce_audio_graph.h index a8b3cffe3..455ae3800 100644 --- a/source/modules/juce_audio_graph/juce_audio_graph.h +++ b/source/modules/juce_audio_graph/juce_audio_graph.h @@ -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" } diff --git a/source/modules/juce_audio_graph/main.cpp b/source/modules/juce_audio_graph/main.cpp new file mode 100644 index 000000000..7e8aa42cb --- /dev/null +++ b/source/modules/juce_audio_graph/main.cpp @@ -0,0 +1,24 @@ + +#include "juce_audio_graph.h" +#include + +using namespace juce2; + +int main() +{ + String x = "haha"; + std::cout << x << std::endl; + + Atomic a; + CharPointer_UTF8 c("c"); + HeapBlock hs; + MemoryBlock m; + Array ar; + OwnedArray ows; + AudioSampleBuffer as; + MidiBuffer mb; + MidiMessage ms; + AudioProcessorGraph g; + + return 0; +} diff --git a/source/modules/juce_audio_graph/memory/juce_ReferenceCountedObject.h b/source/modules/juce_audio_graph/memory/juce_ReferenceCountedObject.h new file mode 100644 index 000000000..010de691a --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_ReferenceCountedObject.h @@ -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 Ptr; + }; + + MyClass::Ptr p = new MyClass(); + MyClass::Ptr p2 = p; + p = nullptr; + p2->foo(); + @endcode + + Once a new ReferenceCountedObject has been assigned to a pointer, be + careful not to delete the object manually. + + This class uses an Atomic value to hold the reference count, so that it + the pointers can be passed between threads safely. For a faster but non-thread-safe + version, use SingleThreadedReferenceCountedObject instead. + + @see ReferenceCountedObjectPtr, ReferenceCountedArray, SingleThreadedReferenceCountedObject +*/ +class JUCE_API ReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + 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 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 Ptr; + ... + @endcode + + @see ReferenceCountedObject, ReferenceCountedObjectArray +*/ +template +class ReferenceCountedObjectPtr +{ +public: + /** The class being referenced by this pointer. */ + typedef ReferenceCountedObjectClass ReferencedType; + + //============================================================================== + /** Creates a pointer to a null object. */ + 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 + ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (static_cast (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 + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (static_cast (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 +bool operator== (const ReferenceCountedObjectPtr& object1, ReferenceCountedObjectClass* const object2) noexcept +{ + return object1.get() == object2; +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() == object2.get(); +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 == object2.get(); +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectClass* object2) noexcept +{ + return object1.get() != object2; +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() != object2.get(); +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 != object2.get(); +} + + +#endif // JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED diff --git a/source/modules/juce_audio_graph/misc/juce_Result.cpp b/source/modules/juce_audio_graph/misc/juce_Result.cpp new file mode 100644 index 000000000..243f2d1c5 --- /dev/null +++ b/source/modules/juce_audio_graph/misc/juce_Result.cpp @@ -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 (other.errorMessage)) +{ +} + +Result& Result::operator= (Result&& other) noexcept +{ + errorMessage = static_cast (other.errorMessage); + return *this; +} +#endif + +bool Result::operator== (const Result& other) const noexcept +{ + return errorMessage == other.errorMessage; +} + +bool Result::operator!= (const Result& other) const noexcept +{ + return errorMessage != other.errorMessage; +} + +Result Result::fail (const String& errorMessage) noexcept +{ + return Result (errorMessage.isEmpty() ? "Unknown Error" : errorMessage); +} + +const String& Result::getErrorMessage() const noexcept +{ + return errorMessage; +} + +bool Result::wasOk() const noexcept { return errorMessage.isEmpty(); } +Result::operator bool() const noexcept { return errorMessage.isEmpty(); } +bool Result::failed() const noexcept { return errorMessage.isNotEmpty(); } +bool Result::operator!() const noexcept { return errorMessage.isNotEmpty(); } diff --git a/source/modules/juce_audio_graph/misc/juce_Result.h b/source/modules/juce_audio_graph/misc/juce_Result.h new file mode 100644 index 000000000..4d047dd63 --- /dev/null +++ b/source/modules/juce_audio_graph/misc/juce_Result.h @@ -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 diff --git a/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h b/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h index 5db476658..414ca55fb 100644 --- a/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h +++ b/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h @@ -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. */ diff --git a/source/modules/juce_audio_graph/streams/juce_FileInputSource.cpp b/source/modules/juce_audio_graph/streams/juce_FileInputSource.cpp new file mode 100644 index 000000000..ee9cb0bd1 --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_FileInputSource.cpp @@ -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; +} diff --git a/source/modules/juce_audio_graph/streams/juce_FileInputSource.h b/source/modules/juce_audio_graph/streams/juce_FileInputSource.h new file mode 100644 index 000000000..33421037f --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_FileInputSource.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 diff --git a/source/modules/juce_audio_graph/streams/juce_FileInputStream.cpp b/source/modules/juce_audio_graph/streams/juce_FileInputStream.cpp new file mode 100644 index 000000000..9ec77881c --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_FileInputStream.cpp @@ -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; +} diff --git a/source/modules/juce_audio_graph/streams/juce_FileInputStream.h b/source/modules/juce_audio_graph/streams/juce_FileInputStream.h new file mode 100644 index 000000000..3f7caa27b --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_FileInputStream.h @@ -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 diff --git a/source/modules/juce_audio_graph/streams/juce_InputSource.h b/source/modules/juce_audio_graph/streams/juce_InputSource.h new file mode 100644 index 000000000..c5153af02 --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_InputSource.h @@ -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 diff --git a/source/modules/juce_audio_graph/text/juce_Identifier.cpp b/source/modules/juce_audio_graph/text/juce_Identifier.cpp new file mode 100644 index 000000000..a16d68caa --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_Identifier.cpp @@ -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 (other.name)) {} + +Identifier& Identifier::operator= (Identifier&& other) noexcept +{ + name = static_cast (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_-:#@$%"); +} diff --git a/source/modules/juce_audio_graph/text/juce_Identifier.h b/source/modules/juce_audio_graph/text/juce_Identifier.h new file mode 100644 index 000000000..24b02375e --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_Identifier.h @@ -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 diff --git a/source/modules/juce_audio_graph/text/juce_StringArray.cpp b/source/modules/juce_audio_graph/text/juce_StringArray.cpp new file mode 100644 index 000000000..5db0b9d24 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_StringArray.cpp @@ -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&&> (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&&> (other.strings); + return *this; +} +#endif + +#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS +StringArray::StringArray (const std::initializer_list& 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 (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(); +} diff --git a/source/modules/juce_audio_graph/text/juce_StringArray.h b/source/modules/juce_audio_graph/text/juce_StringArray.h new file mode 100644 index 000000000..8dd64c310 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_StringArray.h @@ -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& 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 strings; + +private: + JUCE_LEAK_DETECTOR (StringArray) +}; + + +#endif // JUCE_STRINGARRAY_H_INCLUDED diff --git a/source/modules/juce_audio_graph/text/juce_StringPool.cpp b/source/modules/juce_audio_graph/text/juce_StringPool.cpp new file mode 100644 index 000000000..d88820cee --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_StringPool.cpp @@ -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 +static String addPooledString (Array& 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; +} diff --git a/source/modules/juce_audio_graph/text/juce_StringPool.h b/source/modules/juce_audio_graph/text/juce_StringPool.h new file mode 100644 index 000000000..737b80cef --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_StringPool.h @@ -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 strings; + CarlaRecursiveMutex lock; + uint32 lastGarbageCollectionTime; + + void garbageCollectIfNeeded(); + + JUCE_DECLARE_NON_COPYABLE (StringPool) +}; + + +#endif // JUCE_STRINGPOOL_H_INCLUDED diff --git a/source/modules/juce_audio_graph/xml/juce_XmlDocument.cpp b/source/modules/juce_audio_graph/xml/juce_XmlDocument.cpp new file mode 100644 index 000000000..50f4d5ef9 --- /dev/null +++ b/source/modules/juce_audio_graph/xml/juce_XmlDocument.cpp @@ -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 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 (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 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 result (readNextElement (! onlyReadOuterDocumentElement)); + + if (! errorOccurred) + return result.release(); + } + + return nullptr; +} + +bool XmlDocument::parseHeader() +{ + skipNextWhiteSpace(); + + if (CharacterFunctions::compareUpTo (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 (" 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::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::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 (ent.substring (2).getHexValue32())); + + if (char1 >= '0' && char1 <= '9') + return String::charToString (static_cast (ent.substring (1).getIntValue())); + + setLastError ("illegal escape sequence", false); + return String::charToString ('&'); + } + + return expandExternalEntity (ent); +} + +String XmlDocument::expandExternalEntity (const String& entity) +{ + if (needToLoadDTD) + { + if (dtdText.isNotEmpty()) + { + dtdText = dtdText.trimCharactersAtEnd (">"); + tokenisedDTD.addTokens (dtdText, true); + + if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") + && tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) + { + const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); + + tokenisedDTD.clear(); + tokenisedDTD.addTokens (getFileContents (fn), true); + } + else + { + tokenisedDTD.clear(); + const int openBracket = dtdText.indexOfChar ('['); + + if (openBracket > 0) + { + const int closeBracket = dtdText.lastIndexOfChar (']'); + + if (closeBracket > openBracket) + tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, + closeBracket), true); + } + } + + for (int i = tokenisedDTD.size(); --i >= 0;) + { + if (tokenisedDTD[i].startsWithChar ('%') + && tokenisedDTD[i].endsWithChar (';')) + { + const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); + StringArray newToks; + newToks.addTokens (parsed, true); + + tokenisedDTD.remove (i); + + for (int j = newToks.size(); --j >= 0;) + tokenisedDTD.insert (i, newToks[j]); + } + } + } + + needToLoadDTD = false; + } + + for (int i = 0; i < tokenisedDTD.size(); ++i) + { + if (tokenisedDTD[i] == entity) + { + if (tokenisedDTD[i - 1].equalsIgnoreCase ("").trim().unquoted()); + + // check for sub-entities.. + int ampersand = ent.indexOfChar ('&'); + + while (ampersand >= 0) + { + const int semiColon = ent.indexOf (i + 1, ";"); + + if (semiColon < 0) + { + setLastError ("entity without terminating semi-colon", false); + break; + } + + const String resolved (expandEntity (ent.substring (i + 1, semiColon))); + + ent = ent.substring (0, ampersand) + + resolved + + ent.substring (semiColon + 1); + + ampersand = ent.indexOfChar (semiColon + 1, '&'); + } + + return ent; + } + } + } + + setLastError ("unknown entity", true); + + return entity; +} + +String XmlDocument::getParameterEntity (const String& entity) +{ + for (int i = 0; i < tokenisedDTD.size(); ++i) + { + if (tokenisedDTD[i] == entity + && tokenisedDTD [i - 1] == "%" + && tokenisedDTD [i - 2].equalsIgnoreCase ("")); + + if (ent.equalsIgnoreCase ("system")) + return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); + + return ent.trim().unquoted(); + } + } + + return entity; +} diff --git a/source/modules/juce_audio_graph/xml/juce_XmlDocument.h b/source/modules/juce_audio_graph/xml/juce_XmlDocument.h new file mode 100644 index 000000000..fad2a38d9 --- /dev/null +++ b/source/modules/juce_audio_graph/xml/juce_XmlDocument.h @@ -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 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 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; + + 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 diff --git a/source/modules/juce_audio_graph/xml/juce_XmlElement.cpp b/source/modules/juce_audio_graph/xml/juce_XmlElement.cpp new file mode 100644 index 000000000..d4b880370 --- /dev/null +++ b/source/modules/juce_audio_graph/xml/juce_XmlElement.cpp @@ -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&&> (other.nextListItem)), + firstChildElement (static_cast&&> (other.firstChildElement)), + attributes (static_cast&&> (other.attributes)), + tagName (static_cast (other.tagName)) +{ +} + +XmlElement& XmlElement::operator= (XmlElement&& other) noexcept +{ + jassert (this != &other); // hopefully the compiler should make this situation impossible! + + removeAllAttributes(); + deleteAllChildElements(); + + nextListItem = static_cast&&> (other.nextListItem); + firstChildElement = static_cast&&> (other.firstChildElement); + attributes = static_cast&&> (other.attributes); + tagName = static_cast (other.tagName); + + return *this; +} +#endif + +void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) +{ + jassert (firstChildElement.get() == nullptr); + firstChildElement.addCopyOfList (other.firstChildElement); + + jassert (attributes.get() == nullptr); + attributes.addCopyOfList (other.attributes); +} + +XmlElement::~XmlElement() noexcept +{ + firstChildElement.deleteAll(); + attributes.deleteAll(); +} + +//============================================================================== +namespace XmlOutputFunctions +{ + #if 0 // (These functions are just used to generate the lookup table used below) + bool isLegalXmlCharSlow (const juce_wchar character) noexcept + { + if ((character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9')) + return true; + + const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; + + do + { + if (((juce_wchar) (uint8) *t) == character) + return true; + } + while (*++t != 0); + + return false; + } + + void generateLegalCharLookupTable() + { + uint8 n[32] = { 0 }; + for (int i = 0; i < 256; ++i) + if (isLegalXmlCharSlow (i)) + n[i >> 3] |= (1 << (i & 7)); + + String s; + for (int i = 0; i < 32; ++i) + s << (int) n[i] << ", "; + + DBG (s); + } + #endif + + static bool isLegalXmlChar (const uint32 c) noexcept + { + static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, + 255, 255, 191, 254, 255, 255, 127 }; + return c < sizeof (legalChars) * 8 + && (legalChars [c >> 3] & (1 << (c & 7))) != 0; + } + + static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) + { + String::CharPointerType t (text.getCharPointer()); + + for (;;) + { + const uint32 character = (uint32) t.getAndAdvance(); + + if (character == 0) + break; + + if (isLegalXmlChar (character)) + { + outputStream << (char) character; + } + else + { + switch (character) + { + case '&': outputStream << "&"; break; + case '"': outputStream << """; break; + case '>': outputStream << ">"; break; + case '<': outputStream << "<"; break; + + case '\n': + case '\r': + if (! changeNewLines) + { + outputStream << (char) character; + break; + } + // Note: deliberate fall-through here! + default: + outputStream << "&#" << ((int) character) << ';'; + break; + } + } + } + } + + static void writeSpaces (OutputStream& out, const 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 ("'); + } + 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 << ""; + + 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* 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* 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; + } +} diff --git a/source/modules/juce_audio_graph/xml/juce_XmlElement.h b/source/modules/juce_audio_graph/xml/juce_XmlElement.h new file mode 100644 index 000000000..1d9da108f --- /dev/null +++ b/source/modules/juce_audio_graph/xml/juce_XmlElement.h @@ -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 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 ", 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 \, this would + return 2. + */ + int getNumAttributes() const noexcept; + + /** Returns the name of one of the elements attributes. + + E.g. for an element such as \, then + getAttributeName(1) would return "antlers". + + @see getAttributeValue, getStringAttribute + */ + const String& getAttributeName (int attributeIndex) const noexcept; + + /** Returns the value of one of the elements attributes. + + E.g. for an element such as \, then + getAttributeName(1) would return "2". + + @see getAttributeName, getStringAttribute + */ + const String& getAttributeValue (int attributeIndex) const noexcept; + + //============================================================================== + // Attribute-handling methods.. + + /** Checks whether the element contains an attribute with a certain name. */ + bool hasAttribute (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 + void sortChildElements (ElementComparator& comparator, + bool retainOrderOfEquivalentItems = false) + { + const int num = getNumChildElements(); + + if (num > 1) + { + HeapBlock elems ((size_t) num); + getChildElementsAsArray (elems); + sortArray (comparator, (XmlElement**) elems, 0, num - 1, retainOrderOfEquivalentItems); + reorderChildElements (elems, num); + } + } + + //============================================================================== + /** Returns true if this element is a section of text. + + Elements can either be an XML tag element or a secton of text, so this + is used to find out what kind of element this one is. + + @see getAllText, addTextElement, deleteAllTextElements + */ + bool isTextElement() const noexcept; + + /** Returns the text for a text element. + + Note that if you have an element like this: + + @codehello@endcode + + then calling getText on the "xyz" element won't return "hello", because that is + actually stored in a special text sub-element inside the xyz element. To get the + "hello" string, you could either call getText on the (unnamed) sub-element, or + use getAllSubText() to do this automatically. + + Note that leading and trailing whitespace will be included in the string - to remove + if, just call String::trim() on the result. + + @see isTextElement, getAllSubText, getChildElementAllSubText + */ + const String& getText() const noexcept; + + /** Sets the text in a text element. + + Note that this is only a valid call if this element is a text element. If it's + not, then no action will be performed. If you're trying to add text inside a normal + element, you probably want to use addTextElement() instead. + */ + void setText (const String& newText); + + /** Returns all the text from this element's child nodes. + + This iterates all the child elements and when it finds text elements, + it concatenates their text into a big string which it returns. + + E.g. @codehello there world@endcode + if you called getAllSubText on the "xyz" element, it'd return "hello there world". + + Note that leading and trailing whitespace will be included in the string - to remove + if, just call String::trim() on the result. + + @see isTextElement, getChildElementAllSubText, getText, addTextElement + */ + String getAllSubText() const; + + /** Returns all the sub-text of a named child element. + + If there is a child element with the given tag name, this will return + all of its sub-text (by calling getAllSubText() on it). If there is + no such child element, this will return the default string passed-in. + + @see getAllSubText + */ + String getChildElementAllSubText (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 nextListItem; + Identifier name; + String value; + + private: + XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION; + }; + + friend class XmlDocument; + friend class LinkedListPointer; + friend class LinkedListPointer; + friend class LinkedListPointer::Appender; + friend class NamedValueSet; + + LinkedListPointer nextListItem; + LinkedListPointer firstChildElement; + LinkedListPointer attributes; + String tagName; + + XmlElement (int) noexcept; + void copyChildrenAndAttributesFrom (const XmlElement&); + void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const; + void getChildElementsAsArray (XmlElement**) const noexcept; + void reorderChildElements (XmlElement**, int) noexcept; + 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