@@ -199,3 +199,6 @@ data/macos/*.*/ | |||||
data/macos/Carla/ | data/macos/Carla/ | ||||
data/windows/python/ | data/windows/python/ | ||||
source/modules/juce_audio_graph/test | |||||
source/modules/juce_audio_graph/test.d |
@@ -569,7 +569,7 @@ public: | |||||
{ | { | ||||
float* const d = channels [channel] + startSample; | float* const d = channels [channel] + startSample; | ||||
if (gain == 0.0f) | |||||
if (carla_isZero (gain)) | |||||
carla_zeroFloats (d, numSamples); | carla_zeroFloats (d, numSamples); | ||||
else | else | ||||
carla_multiply (d, gain, numSamples); | carla_multiply (d, gain, numSamples); | ||||
@@ -0,0 +1,373 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_LINKEDLISTPOINTER_H_INCLUDED | |||||
#define JUCE_LINKEDLISTPOINTER_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Helps to manipulate singly-linked lists of objects. | |||||
For objects that are designed to contain a pointer to the subsequent item in the | |||||
list, this class contains methods to deal with the list. To use it, the ObjectType | |||||
class that it points to must contain a LinkedListPointer called nextListItem, e.g. | |||||
@code | |||||
struct MyObject | |||||
{ | |||||
int x, y, z; | |||||
// A linkable object must contain a member with this name and type, which must be | |||||
// accessible by the LinkedListPointer class. (This doesn't mean it has to be public - | |||||
// you could make your class a friend of a LinkedListPointer<MyObject> instead). | |||||
LinkedListPointer<MyObject> nextListItem; | |||||
}; | |||||
LinkedListPointer<MyObject> myList; | |||||
myList.append (new MyObject()); | |||||
myList.append (new MyObject()); | |||||
int numItems = myList.size(); // returns 2 | |||||
MyObject* lastInList = myList.getLast(); | |||||
@endcode | |||||
*/ | |||||
template <class ObjectType> | |||||
class LinkedListPointer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a null pointer to an empty list. */ | |||||
LinkedListPointer() noexcept | |||||
: item (nullptr) | |||||
{ | |||||
} | |||||
/** Creates a pointer to a list whose head is the item provided. */ | |||||
explicit LinkedListPointer (ObjectType* const headItem) noexcept | |||||
: item (headItem) | |||||
{ | |||||
} | |||||
/** Sets this pointer to point to a new list. */ | |||||
LinkedListPointer& operator= (ObjectType* const newItem) noexcept | |||||
{ | |||||
item = newItem; | |||||
return *this; | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
LinkedListPointer (LinkedListPointer&& other) noexcept | |||||
: item (other.item) | |||||
{ | |||||
other.item = nullptr; | |||||
} | |||||
LinkedListPointer& operator= (LinkedListPointer&& other) noexcept | |||||
{ | |||||
jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||||
item = other.item; | |||||
other.item = nullptr; | |||||
return *this; | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
/** Returns the item which this pointer points to. */ | |||||
inline operator ObjectType*() const noexcept | |||||
{ | |||||
return item; | |||||
} | |||||
/** Returns the item which this pointer points to. */ | |||||
inline ObjectType* get() const noexcept | |||||
{ | |||||
return item; | |||||
} | |||||
/** Returns the last item in the list which this pointer points to. | |||||
This will iterate the list and return the last item found. Obviously the speed | |||||
of this operation will be proportional to the size of the list. If the list is | |||||
empty the return value will be this object. | |||||
If you're planning on appending a number of items to your list, it's much more | |||||
efficient to use the Appender class than to repeatedly call getLast() to find the end. | |||||
*/ | |||||
LinkedListPointer& getLast() noexcept | |||||
{ | |||||
LinkedListPointer* l = this; | |||||
while (l->item != nullptr) | |||||
l = &(l->item->nextListItem); | |||||
return *l; | |||||
} | |||||
/** Returns the number of items in the list. | |||||
Obviously with a simple linked list, getting the size involves iterating the list, so | |||||
this can be a lengthy operation - be careful when using this method in your code. | |||||
*/ | |||||
int size() const noexcept | |||||
{ | |||||
int total = 0; | |||||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||||
++total; | |||||
return total; | |||||
} | |||||
/** Returns the item at a given index in the list. | |||||
Since the only way to find an item is to iterate the list, this operation can obviously | |||||
be slow, depending on its size, so you should be careful when using this in algorithms. | |||||
*/ | |||||
LinkedListPointer& operator[] (int index) noexcept | |||||
{ | |||||
LinkedListPointer* l = this; | |||||
while (--index >= 0 && l->item != nullptr) | |||||
l = &(l->item->nextListItem); | |||||
return *l; | |||||
} | |||||
/** Returns the item at a given index in the list. | |||||
Since the only way to find an item is to iterate the list, this operation can obviously | |||||
be slow, depending on its size, so you should be careful when using this in algorithms. | |||||
*/ | |||||
const LinkedListPointer& operator[] (int index) const noexcept | |||||
{ | |||||
const LinkedListPointer* l = this; | |||||
while (--index >= 0 && l->item != nullptr) | |||||
l = &(l->item->nextListItem); | |||||
return *l; | |||||
} | |||||
/** Returns true if the list contains the given item. */ | |||||
bool contains (const ObjectType* const itemToLookFor) const noexcept | |||||
{ | |||||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||||
if (itemToLookFor == i) | |||||
return true; | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
/** Inserts an item into the list, placing it before the item that this pointer | |||||
currently points to. | |||||
*/ | |||||
void insertNext (ObjectType* const newItem) | |||||
{ | |||||
jassert (newItem != nullptr); | |||||
jassert (newItem->nextListItem == nullptr); | |||||
newItem->nextListItem = item; | |||||
item = newItem; | |||||
} | |||||
/** Inserts an item at a numeric index in the list. | |||||
Obviously this will involve iterating the list to find the item at the given index, | |||||
so be careful about the impact this may have on execution time. | |||||
*/ | |||||
void insertAtIndex (int index, ObjectType* newItem) | |||||
{ | |||||
jassert (newItem != nullptr); | |||||
LinkedListPointer* l = this; | |||||
while (index != 0 && l->item != nullptr) | |||||
{ | |||||
l = &(l->item->nextListItem); | |||||
--index; | |||||
} | |||||
l->insertNext (newItem); | |||||
} | |||||
/** Replaces the object that this pointer points to, appending the rest of the list to | |||||
the new object, and returning the old one. | |||||
*/ | |||||
ObjectType* replaceNext (ObjectType* const newItem) noexcept | |||||
{ | |||||
jassert (newItem != nullptr); | |||||
jassert (newItem->nextListItem == nullptr); | |||||
ObjectType* const oldItem = item; | |||||
item = newItem; | |||||
item->nextListItem = oldItem->nextListItem.item; | |||||
oldItem->nextListItem.item = nullptr; | |||||
return oldItem; | |||||
} | |||||
/** Adds an item to the end of the list. | |||||
This operation involves iterating the whole list, so can be slow - if you need to | |||||
append a number of items to your list, it's much more efficient to use the Appender | |||||
class than to repeatedly call append(). | |||||
*/ | |||||
void append (ObjectType* const newItem) | |||||
{ | |||||
getLast().item = newItem; | |||||
} | |||||
/** Creates copies of all the items in another list and adds them to this one. | |||||
This will use the ObjectType's copy constructor to try to create copies of each | |||||
item in the other list, and appends them to this list. | |||||
*/ | |||||
void addCopyOfList (const LinkedListPointer& other) | |||||
{ | |||||
LinkedListPointer* insertPoint = this; | |||||
for (ObjectType* i = other.item; i != nullptr; i = i->nextListItem) | |||||
{ | |||||
insertPoint->insertNext (new ObjectType (*i)); | |||||
insertPoint = &(insertPoint->item->nextListItem); | |||||
} | |||||
} | |||||
/** Removes the head item from the list. | |||||
This won't delete the object that is removed, but returns it, so the caller can | |||||
delete it if necessary. | |||||
*/ | |||||
ObjectType* removeNext() noexcept | |||||
{ | |||||
ObjectType* const oldItem = item; | |||||
if (oldItem != nullptr) | |||||
{ | |||||
item = oldItem->nextListItem; | |||||
oldItem->nextListItem.item = nullptr; | |||||
} | |||||
return oldItem; | |||||
} | |||||
/** Removes a specific item from the list. | |||||
Note that this will not delete the item, it simply unlinks it from the list. | |||||
*/ | |||||
void remove (ObjectType* const itemToRemove) | |||||
{ | |||||
if (LinkedListPointer* const l = findPointerTo (itemToRemove)) | |||||
l->removeNext(); | |||||
} | |||||
/** Iterates the list, calling the delete operator on all of its elements and | |||||
leaving this pointer empty. | |||||
*/ | |||||
void deleteAll() | |||||
{ | |||||
while (item != nullptr) | |||||
{ | |||||
ObjectType* const oldItem = item; | |||||
item = oldItem->nextListItem; | |||||
delete oldItem; | |||||
} | |||||
} | |||||
/** Finds a pointer to a given item. | |||||
If the item is found in the list, this returns the pointer that points to it. If | |||||
the item isn't found, this returns null. | |||||
*/ | |||||
LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept | |||||
{ | |||||
LinkedListPointer* l = this; | |||||
while (l->item != nullptr) | |||||
{ | |||||
if (l->item == itemToLookFor) | |||||
return l; | |||||
l = &(l->item->nextListItem); | |||||
} | |||||
return nullptr; | |||||
} | |||||
/** Copies the items in the list to an array. | |||||
The destArray must contain enough elements to hold the entire list - no checks are | |||||
made for this! | |||||
*/ | |||||
void copyToArray (ObjectType** destArray) const noexcept | |||||
{ | |||||
jassert (destArray != nullptr); | |||||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||||
*destArray++ = i; | |||||
} | |||||
/** Swaps this pointer with another one */ | |||||
void swapWith (LinkedListPointer& other) noexcept | |||||
{ | |||||
std::swap (item, other.item); | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
Allows efficient repeated insertions into a list. | |||||
You can create an Appender object which points to the last element in your | |||||
list, and then repeatedly call Appender::append() to add items to the end | |||||
of the list in O(1) time. | |||||
*/ | |||||
class Appender | |||||
{ | |||||
public: | |||||
/** Creates an appender which will add items to the given list. | |||||
*/ | |||||
Appender (LinkedListPointer& endOfListPointer) noexcept | |||||
: endOfList (&endOfListPointer) | |||||
{ | |||||
// This can only be used to add to the end of a list. | |||||
jassert (endOfListPointer.item == nullptr); | |||||
} | |||||
/** Appends an item to the list. */ | |||||
void append (ObjectType* const newItem) noexcept | |||||
{ | |||||
*endOfList = newItem; | |||||
endOfList = &(newItem->nextListItem); | |||||
} | |||||
private: | |||||
LinkedListPointer* endOfList; | |||||
JUCE_DECLARE_NON_COPYABLE (Appender) | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
ObjectType* item; | |||||
JUCE_DECLARE_NON_COPYABLE (LinkedListPointer) | |||||
}; | |||||
#endif // JUCE_LINKEDLISTPOINTER_H_INCLUDED |
@@ -0,0 +1,856 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||||
#define JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Holds a list of objects derived from ReferenceCountedObject, or which implement basic | |||||
reference-count handling methods. | |||||
The template parameter specifies the class of the object you want to point to - the easiest | |||||
way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject | |||||
or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||||
class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||||
decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||||
should behave. | |||||
A ReferenceCountedArray holds objects derived from ReferenceCountedObject, | |||||
and takes care of incrementing and decrementing their ref counts when they | |||||
are added and removed from the array. | |||||
To make all the array's methods thread-safe, pass in "CriticalSection" as the templated | |||||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||||
@see Array, OwnedArray, StringArray | |||||
*/ | |||||
template <class ObjectClass> | |||||
class ReferenceCountedArray | |||||
{ | |||||
public: | |||||
typedef ReferenceCountedObjectPtr<ObjectClass> ObjectClassPtr; | |||||
//============================================================================== | |||||
/** Creates an empty array. | |||||
@see ReferenceCountedObject, Array, OwnedArray | |||||
*/ | |||||
ReferenceCountedArray() noexcept | |||||
: numUsed (0) | |||||
{ | |||||
} | |||||
/** Creates a copy of another array */ | |||||
ReferenceCountedArray (const ReferenceCountedArray& other) noexcept | |||||
{ | |||||
numUsed = other.size(); | |||||
data.setAllocatedSize (numUsed); | |||||
memcpy (data.elements, other.getRawDataPointer(), (size_t) numUsed * sizeof (ObjectClass*)); | |||||
for (int i = numUsed; --i >= 0;) | |||||
if (ObjectClass* o = data.elements[i]) | |||||
o->incReferenceCount(); | |||||
} | |||||
/** Creates a copy of another array */ | |||||
template <class OtherObjectClass> | |||||
ReferenceCountedArray (const ReferenceCountedArray<OtherObjectClass>& other) noexcept | |||||
{ | |||||
numUsed = other.size(); | |||||
data.setAllocatedSize (numUsed); | |||||
memcpy (data.elements, other.getRawDataPointer(), numUsed * sizeof (ObjectClass*)); | |||||
for (int i = numUsed; --i >= 0;) | |||||
if (ObjectClass* o = data.elements[i]) | |||||
o->incReferenceCount(); | |||||
} | |||||
/** Copies another array into this one. | |||||
Any existing objects in this array will first be released. | |||||
*/ | |||||
ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept | |||||
{ | |||||
ReferenceCountedArray otherCopy (other); | |||||
swapWith (otherCopy); | |||||
return *this; | |||||
} | |||||
/** Copies another array into this one. | |||||
Any existing objects in this array will first be released. | |||||
*/ | |||||
template <class OtherObjectClass> | |||||
ReferenceCountedArray<ObjectClass>& operator= (const ReferenceCountedArray<OtherObjectClass>& other) noexcept | |||||
{ | |||||
ReferenceCountedArray<ObjectClass> otherCopy (other); | |||||
swapWith (otherCopy); | |||||
return *this; | |||||
} | |||||
/** Destructor. | |||||
Any objects in the array will be released, and may be deleted if not referenced from elsewhere. | |||||
*/ | |||||
~ReferenceCountedArray() | |||||
{ | |||||
releaseAllObjects(); | |||||
} | |||||
//============================================================================== | |||||
/** Removes all objects from the array. | |||||
Any objects in the array that whose reference counts drop to zero will be deleted. | |||||
*/ | |||||
void clear() | |||||
{ | |||||
releaseAllObjects(); | |||||
data.setAllocatedSize (0); | |||||
} | |||||
/** Removes all objects from the array without freeing the array's allocated storage. | |||||
Any objects in the array that whose reference counts drop to zero will be deleted. | |||||
@see clear | |||||
*/ | |||||
void clearQuick() | |||||
{ | |||||
releaseAllObjects(); | |||||
} | |||||
/** Returns the current number of objects in the array. */ | |||||
inline int size() const noexcept | |||||
{ | |||||
return numUsed; | |||||
} | |||||
/** Returns true if the array is empty, false otherwise. */ | |||||
inline bool isEmpty() const noexcept | |||||
{ | |||||
return size() == 0; | |||||
} | |||||
/** Returns a pointer to the object at this index in the array. | |||||
If the index is out-of-range, this will return a null pointer, (and | |||||
it could be null anyway, because it's ok for the array to hold null | |||||
pointers as well as objects). | |||||
@see getUnchecked | |||||
*/ | |||||
inline ObjectClassPtr operator[] (const int index) const noexcept | |||||
{ | |||||
return getObjectPointer (index); | |||||
} | |||||
/** Returns a pointer to the object at this index in the array, without checking | |||||
whether the index is in-range. | |||||
This is a faster and less safe version of operator[] which doesn't check the index passed in, so | |||||
it can be used when you're sure the index is always going to be legal. | |||||
*/ | |||||
inline ObjectClassPtr getUnchecked (const int index) const noexcept | |||||
{ | |||||
return getObjectPointerUnchecked (index); | |||||
} | |||||
/** Returns a raw pointer to the object at this index in the array. | |||||
If the index is out-of-range, this will return a null pointer, (and | |||||
it could be null anyway, because it's ok for the array to hold null | |||||
pointers as well as objects). | |||||
@see getUnchecked | |||||
*/ | |||||
inline ObjectClass* getObjectPointer (const int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, numUsed)) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [index]; | |||||
} | |||||
return ObjectClassPtr(); | |||||
} | |||||
/** Returns a raw pointer to the object at this index in the array, without checking | |||||
whether the index is in-range. | |||||
*/ | |||||
inline ObjectClass* getObjectPointerUnchecked (const int index) const noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); | |||||
return data.elements [index]; | |||||
} | |||||
/** Returns a pointer to the first object in the array. | |||||
This will return a null pointer if the array's empty. | |||||
@see getLast | |||||
*/ | |||||
inline ObjectClassPtr getFirst() const noexcept | |||||
{ | |||||
if (numUsed > 0) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [0]; | |||||
} | |||||
return ObjectClassPtr(); | |||||
} | |||||
/** Returns a pointer to the last object in the array. | |||||
This will return a null pointer if the array's empty. | |||||
@see getFirst | |||||
*/ | |||||
inline ObjectClassPtr getLast() const noexcept | |||||
{ | |||||
if (numUsed > 0) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [numUsed - 1]; | |||||
} | |||||
return ObjectClassPtr(); | |||||
} | |||||
/** Returns a pointer to the actual array data. | |||||
This pointer will only be valid until the next time a non-const method | |||||
is called on the array. | |||||
*/ | |||||
inline ObjectClass** getRawDataPointer() const noexcept | |||||
{ | |||||
return data.elements; | |||||
} | |||||
//============================================================================== | |||||
/** Returns a pointer to the first element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ObjectClass** begin() const noexcept | |||||
{ | |||||
return data.elements; | |||||
} | |||||
/** Returns a pointer to the element which follows the last element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ObjectClass** end() const noexcept | |||||
{ | |||||
return data.elements + numUsed; | |||||
} | |||||
//============================================================================== | |||||
/** Finds the index of the first occurrence of an object in the array. | |||||
@param objectToLookFor the object to look for | |||||
@returns the index at which the object was found, or -1 if it's not found | |||||
*/ | |||||
int indexOf (const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
ObjectClass** e = data.elements.getData(); | |||||
ObjectClass** const endPointer = e + numUsed; | |||||
while (e != endPointer) | |||||
{ | |||||
if (objectToLookFor == *e) | |||||
return static_cast<int> (e - data.elements.getData()); | |||||
++e; | |||||
} | |||||
return -1; | |||||
} | |||||
/** Returns true if the array contains a specified object. | |||||
@param objectToLookFor the object to look for | |||||
@returns true if the object is in the array | |||||
*/ | |||||
bool contains (const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
ObjectClass** e = data.elements.getData(); | |||||
ObjectClass** const endPointer = e + numUsed; | |||||
while (e != endPointer) | |||||
{ | |||||
if (objectToLookFor == *e) | |||||
return true; | |||||
++e; | |||||
} | |||||
return false; | |||||
} | |||||
/** Appends a new object to the end of the array. | |||||
This will increase the new object's reference count. | |||||
@param newObject the new object to add to the array | |||||
@see set, insert, addIfNotAlreadyThere, addSorted, addArray | |||||
*/ | |||||
ObjectClass* add (ObjectClass* const newObject) noexcept | |||||
{ | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
data.elements [numUsed++] = newObject; | |||||
if (newObject != nullptr) | |||||
newObject->incReferenceCount(); | |||||
return newObject; | |||||
} | |||||
/** Inserts a new object into the array at the given index. | |||||
If the index is less than 0 or greater than the size of the array, the | |||||
element will be added to the end of the array. | |||||
Otherwise, it will be inserted into the array, moving all the later elements | |||||
along to make room. | |||||
This will increase the new object's reference count. | |||||
@param indexToInsertAt the index at which the new element should be inserted | |||||
@param newObject the new object to add to the array | |||||
@see add, addSorted, addIfNotAlreadyThere, set | |||||
*/ | |||||
ObjectClass* insert (int indexToInsertAt, | |||||
ObjectClass* const newObject) noexcept | |||||
{ | |||||
if (indexToInsertAt < 0) | |||||
return add (newObject); | |||||
if (indexToInsertAt > numUsed) | |||||
indexToInsertAt = numUsed; | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
ObjectClass** const e = data.elements + indexToInsertAt; | |||||
const int numToMove = numUsed - indexToInsertAt; | |||||
if (numToMove > 0) | |||||
memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); | |||||
*e = newObject; | |||||
if (newObject != nullptr) | |||||
newObject->incReferenceCount(); | |||||
++numUsed; | |||||
return newObject; | |||||
} | |||||
/** Appends a new object at the end of the array as long as the array doesn't | |||||
already contain it. | |||||
If the array already contains a matching object, nothing will be done. | |||||
@param newObject the new object to add to the array | |||||
@returns true if the object has been added, false otherwise | |||||
*/ | |||||
bool addIfNotAlreadyThere (ObjectClass* const newObject) noexcept | |||||
{ | |||||
if (contains (newObject)) | |||||
return false; | |||||
add (newObject); | |||||
return true; | |||||
} | |||||
/** Replaces an object in the array with a different one. | |||||
If the index is less than zero, this method does nothing. | |||||
If the index is beyond the end of the array, the new object is added to the end of the array. | |||||
The object being added has its reference count increased, and if it's replacing | |||||
another object, then that one has its reference count decreased, and may be deleted. | |||||
@param indexToChange the index whose value you want to change | |||||
@param newObject the new value to set for this index. | |||||
@see add, insert, remove | |||||
*/ | |||||
void set (const int indexToChange, | |||||
ObjectClass* const newObject) | |||||
{ | |||||
if (indexToChange >= 0) | |||||
{ | |||||
if (newObject != nullptr) | |||||
newObject->incReferenceCount(); | |||||
if (indexToChange < numUsed) | |||||
{ | |||||
if (ObjectClass* o = data.elements [indexToChange]) | |||||
releaseObject (o); | |||||
data.elements [indexToChange] = newObject; | |||||
} | |||||
else | |||||
{ | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
data.elements [numUsed++] = newObject; | |||||
} | |||||
} | |||||
} | |||||
/** Adds elements from another array to the end of this array. | |||||
@param arrayToAddFrom the array from which to copy the elements | |||||
@param startIndex the first element of the other array to start copying from | |||||
@param numElementsToAdd how many elements to add from the other array. If this | |||||
value is negative or greater than the number of available elements, | |||||
all available elements will be copied. | |||||
@see add | |||||
*/ | |||||
void addArray (const ReferenceCountedArray<ObjectClass>& arrayToAddFrom, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1) noexcept | |||||
{ | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) | |||||
numElementsToAdd = arrayToAddFrom.size() - startIndex; | |||||
if (numElementsToAdd > 0) | |||||
{ | |||||
data.ensureAllocatedSize (numUsed + numElementsToAdd); | |||||
while (--numElementsToAdd >= 0) | |||||
add (arrayToAddFrom.getUnchecked (startIndex++)); | |||||
} | |||||
} | |||||
/** Inserts a new object into the array assuming that the array is sorted. | |||||
This will use a comparator to find the position at which the new object | |||||
should go. If the array isn't sorted, the behaviour of this | |||||
method will be unpredictable. | |||||
@param comparator the comparator object to use to compare the elements - see the | |||||
sort() method for details about this object's form | |||||
@param newObject the new object to insert to the array | |||||
@returns the index at which the new object was added | |||||
@see add, sort | |||||
*/ | |||||
template <class ElementComparator> | |||||
int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept | |||||
{ | |||||
const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); | |||||
insert (index, newObject); | |||||
return index; | |||||
} | |||||
/** Inserts or replaces an object in the array, assuming it is sorted. | |||||
This is similar to addSorted, but if a matching element already exists, then it will be | |||||
replaced by the new one, rather than the new one being added as well. | |||||
*/ | |||||
template <class ElementComparator> | |||||
void addOrReplaceSorted (ElementComparator& comparator, | |||||
ObjectClass* newObject) noexcept | |||||
{ | |||||
const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); | |||||
if (index > 0 && comparator.compareElements (newObject, data.elements [index - 1]) == 0) | |||||
set (index - 1, newObject); // replace an existing object that matches | |||||
else | |||||
insert (index, newObject); // no match, so insert the new one | |||||
} | |||||
/** Finds the index of an object in the array, assuming that the array is sorted. | |||||
This will use a comparator to do a binary-chop to find the index of the given | |||||
element, if it exists. If the array isn't sorted, the behaviour of this | |||||
method will be unpredictable. | |||||
@param comparator the comparator to use to compare the elements - see the sort() | |||||
method for details about the form this object should take | |||||
@param objectToLookFor the object to search for | |||||
@returns the index of the element, or -1 if it's not found | |||||
@see addSorted, sort | |||||
*/ | |||||
template <class ElementComparator> | |||||
int indexOfSorted (ElementComparator& comparator, | |||||
const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
ignoreUnused (comparator); | |||||
int s = 0, e = numUsed; | |||||
while (s < e) | |||||
{ | |||||
if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) | |||||
return s; | |||||
const int halfway = (s + e) / 2; | |||||
if (halfway == s) | |||||
break; | |||||
if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) | |||||
s = halfway; | |||||
else | |||||
e = halfway; | |||||
} | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
/** Removes an object from the array. | |||||
This will remove the object at a given index and move back all the | |||||
subsequent objects to close the gap. | |||||
If the index passed in is out-of-range, nothing will happen. | |||||
The object that is removed will have its reference count decreased, | |||||
and may be deleted if not referenced from elsewhere. | |||||
@param indexToRemove the index of the element to remove | |||||
@see removeObject, removeRange | |||||
*/ | |||||
void remove (const int indexToRemove) | |||||
{ | |||||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||||
{ | |||||
ObjectClass** const e = data.elements + indexToRemove; | |||||
if (ObjectClass* o = *e) | |||||
releaseObject (o); | |||||
--numUsed; | |||||
const int numberToShift = numUsed - indexToRemove; | |||||
if (numberToShift > 0) | |||||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
} | |||||
/** Removes and returns an object from the array. | |||||
This will remove the object at a given index and return it, moving back all | |||||
the subsequent objects to close the gap. If the index passed in is out-of-range, | |||||
nothing will happen and a null pointer will be returned. | |||||
@param indexToRemove the index of the element to remove | |||||
@see remove, removeObject, removeRange | |||||
*/ | |||||
ObjectClassPtr removeAndReturn (const int indexToRemove) | |||||
{ | |||||
ObjectClassPtr removedItem; | |||||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||||
{ | |||||
ObjectClass** const e = data.elements + indexToRemove; | |||||
if (ObjectClass* o = *e) | |||||
{ | |||||
removedItem = o; | |||||
releaseObject (o); | |||||
} | |||||
--numUsed; | |||||
const int numberToShift = numUsed - indexToRemove; | |||||
if (numberToShift > 0) | |||||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
return removedItem; | |||||
} | |||||
/** Removes the first occurrence of a specified object from the array. | |||||
If the item isn't found, no action is taken. If it is found, it is | |||||
removed and has its reference count decreased. | |||||
@param objectToRemove the object to try to remove | |||||
@see remove, removeRange | |||||
*/ | |||||
void removeObject (ObjectClass* const objectToRemove) | |||||
{ | |||||
remove (indexOf (objectToRemove)); | |||||
} | |||||
/** Removes a range of objects from the array. | |||||
This will remove a set of objects, starting from the given index, | |||||
and move any subsequent elements down to close the gap. | |||||
If the range extends beyond the bounds of the array, it will | |||||
be safely clipped to the size of the array. | |||||
The objects that are removed will have their reference counts decreased, | |||||
and may be deleted if not referenced from elsewhere. | |||||
@param startIndex the index of the first object to remove | |||||
@param numberToRemove how many objects should be removed | |||||
@see remove, removeObject | |||||
*/ | |||||
void removeRange (const int startIndex, | |||||
const int numberToRemove) | |||||
{ | |||||
const int start = jlimit (0, numUsed, startIndex); | |||||
const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); | |||||
if (endIndex > start) | |||||
{ | |||||
int i; | |||||
for (i = start; i < endIndex; ++i) | |||||
{ | |||||
if (ObjectClass* o = data.elements[i]) | |||||
{ | |||||
releaseObject (o); | |||||
data.elements[i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) | |||||
} | |||||
} | |||||
const int rangeSize = endIndex - start; | |||||
ObjectClass** e = data.elements + start; | |||||
i = numUsed - endIndex; | |||||
numUsed -= rangeSize; | |||||
while (--i >= 0) | |||||
{ | |||||
*e = e [rangeSize]; | |||||
++e; | |||||
} | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
} | |||||
/** Removes the last n objects from the array. | |||||
The objects that are removed will have their reference counts decreased, | |||||
and may be deleted if not referenced from elsewhere. | |||||
@param howManyToRemove how many objects to remove from the end of the array | |||||
@see remove, removeObject, removeRange | |||||
*/ | |||||
void removeLast (int howManyToRemove = 1) | |||||
{ | |||||
if (howManyToRemove > numUsed) | |||||
howManyToRemove = numUsed; | |||||
while (--howManyToRemove >= 0) | |||||
remove (numUsed - 1); | |||||
} | |||||
/** Swaps a pair of objects in the array. | |||||
If either of the indexes passed in is out-of-range, nothing will happen, | |||||
otherwise the two objects at these positions will be exchanged. | |||||
*/ | |||||
void swap (const int index1, | |||||
const int index2) noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index1, numUsed) | |||||
&& isPositiveAndBelow (index2, numUsed)) | |||||
{ | |||||
std::swap (data.elements [index1], | |||||
data.elements [index2]); | |||||
} | |||||
} | |||||
/** Moves one of the objects to a different position. | |||||
This will move the object to a specified index, shuffling along | |||||
any intervening elements as required. | |||||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling | |||||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||||
@param currentIndex the index of the object to be moved. If this isn't a | |||||
valid index, then nothing will be done | |||||
@param newIndex the index at which you'd like this object to end up. If this | |||||
is less than zero, it will be moved to the end of the array | |||||
*/ | |||||
void move (const int currentIndex, | |||||
int newIndex) noexcept | |||||
{ | |||||
if (currentIndex != newIndex) | |||||
{ | |||||
if (isPositiveAndBelow (currentIndex, numUsed)) | |||||
{ | |||||
if (! isPositiveAndBelow (newIndex, numUsed)) | |||||
newIndex = numUsed - 1; | |||||
ObjectClass* const value = data.elements [currentIndex]; | |||||
if (newIndex > currentIndex) | |||||
{ | |||||
memmove (data.elements + currentIndex, | |||||
data.elements + currentIndex + 1, | |||||
sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); | |||||
} | |||||
else | |||||
{ | |||||
memmove (data.elements + newIndex + 1, | |||||
data.elements + newIndex, | |||||
sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); | |||||
} | |||||
data.elements [newIndex] = value; | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** This swaps the contents of this array with those of another array. | |||||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||||
because it just swaps their internal pointers. | |||||
*/ | |||||
template <class OtherArrayType> | |||||
void swapWith (OtherArrayType& otherArray) noexcept | |||||
{ | |||||
data.swapWith (otherArray.data); | |||||
std::swap (numUsed, otherArray.numUsed); | |||||
} | |||||
//============================================================================== | |||||
/** Compares this array to another one. | |||||
@returns true only if the other array contains the same objects in the same order | |||||
*/ | |||||
bool operator== (const ReferenceCountedArray& other) const noexcept | |||||
{ | |||||
if (numUsed != other.numUsed) | |||||
return false; | |||||
for (int i = numUsed; --i >= 0;) | |||||
if (data.elements [i] != other.data.elements [i]) | |||||
return false; | |||||
return true; | |||||
} | |||||
/** Compares this array to another one. | |||||
@see operator== | |||||
*/ | |||||
bool operator!= (const ReferenceCountedArray<ObjectClass>& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
/** Sorts the elements in the array. | |||||
This will use a comparator object to sort the elements into order. The object | |||||
passed must have a method of the form: | |||||
@code | |||||
int compareElements (ElementType first, ElementType second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator the comparator to use for comparing elements. | |||||
@param retainOrderOfEquivalentItems if this is true, then items | |||||
which the comparator says are equivalent will be | |||||
kept in the order in which they currently appear | |||||
in the array. This is slower to perform, but may | |||||
be important in some cases. If it's false, a faster | |||||
algorithm is used, but equivalent elements may be | |||||
rearranged. | |||||
@see sortArray | |||||
*/ | |||||
template <class ElementComparator> | |||||
void sort (ElementComparator& comparator, | |||||
const bool retainOrderOfEquivalentItems = false) const noexcept | |||||
{ | |||||
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this | |||||
// avoids getting warning messages about the parameter being unused | |||||
sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); | |||||
} | |||||
//============================================================================== | |||||
/** Reduces the amount of storage being used by the array. | |||||
Arrays typically allocate slightly more storage than they need, and after | |||||
removing elements, they may have quite a lot of unused space allocated. | |||||
This method will reduce the amount of allocated storage to a minimum. | |||||
*/ | |||||
void minimiseStorageOverheads() noexcept | |||||
{ | |||||
data.shrinkToNoMoreThan (numUsed); | |||||
} | |||||
/** Increases the array's internal storage to hold a minimum number of elements. | |||||
Calling this before adding a large known number of elements means that | |||||
the array won't have to keep dynamically resizing itself as the elements | |||||
are added, and it'll therefore be more efficient. | |||||
*/ | |||||
void ensureStorageAllocated (const int minNumElements) | |||||
{ | |||||
data.ensureAllocatedSize (minNumElements); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
ArrayAllocationBase <ObjectClass*> data; | |||||
int numUsed; | |||||
void releaseAllObjects() | |||||
{ | |||||
while (numUsed > 0) | |||||
if (ObjectClass* o = data.elements [--numUsed]) | |||||
releaseObject (o); | |||||
jassert (numUsed == 0); | |||||
} | |||||
static void releaseObject (ObjectClass* o) | |||||
{ | |||||
if (o->decReferenceCountWithoutDeleting()) | |||||
delete o; | |||||
} | |||||
}; | |||||
#endif // JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED |
@@ -0,0 +1,469 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_SORTEDSET_H_INCLUDED | |||||
#define JUCE_SORTEDSET_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Holds a set of unique primitive objects, such as ints or doubles. | |||||
A set can only hold one item with a given value, so if for example it's a | |||||
set of integers, attempting to add the same integer twice will do nothing | |||||
the second time. | |||||
Internally, the list of items is kept sorted (which means that whatever | |||||
kind of primitive type is used must support the ==, <, >, <= and >= operators | |||||
to determine the order), and searching the set for known values is very fast | |||||
because it uses a binary-chop method. | |||||
Note that if you're using a class or struct as the element type, it must be | |||||
capable of being copied or moved with a straightforward memcpy, rather than | |||||
needing construction and destruction code. | |||||
To make all the set's methods thread-safe, pass in "CriticalSection" as the templated | |||||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||||
@see Array, OwnedArray, ReferenceCountedArray, StringArray, CriticalSection | |||||
*/ | |||||
template <class ElementType> | |||||
class SortedSet | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty set. */ | |||||
SortedSet() noexcept | |||||
{ | |||||
} | |||||
/** Creates a copy of another set. | |||||
@param other the set to copy | |||||
*/ | |||||
SortedSet (const SortedSet& other) | |||||
: data (other.data) | |||||
{ | |||||
} | |||||
/** Destructor. */ | |||||
~SortedSet() noexcept | |||||
{ | |||||
} | |||||
/** Copies another set over this one. | |||||
@param other the set to copy | |||||
*/ | |||||
SortedSet& operator= (const SortedSet& other) noexcept | |||||
{ | |||||
data = other.data; | |||||
return *this; | |||||
} | |||||
//============================================================================== | |||||
/** Compares this set to another one. | |||||
Two sets are considered equal if they both contain the same set of elements. | |||||
@param other the other set to compare with | |||||
*/ | |||||
bool operator== (const SortedSet<ElementType>& other) const noexcept | |||||
{ | |||||
return data == other.data; | |||||
} | |||||
/** Compares this set to another one. | |||||
Two sets are considered equal if they both contain the same set of elements. | |||||
@param other the other set to compare with | |||||
*/ | |||||
bool operator!= (const SortedSet<ElementType>& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
/** Removes all elements from the set. | |||||
This will remove all the elements, and free any storage that the set is | |||||
using. To clear it without freeing the storage, use the clearQuick() | |||||
method instead. | |||||
@see clearQuick | |||||
*/ | |||||
void clear() noexcept | |||||
{ | |||||
data.clear(); | |||||
} | |||||
/** Removes all elements from the set without freeing the array's allocated storage. | |||||
@see clear | |||||
*/ | |||||
void clearQuick() noexcept | |||||
{ | |||||
data.clearQuick(); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the current number of elements in the set. */ | |||||
inline int size() const noexcept | |||||
{ | |||||
return data.size(); | |||||
} | |||||
/** Returns true if the set is empty, false otherwise. */ | |||||
inline bool isEmpty() const noexcept | |||||
{ | |||||
return size() == 0; | |||||
} | |||||
/** Returns one of the elements in the set. | |||||
If the index passed in is beyond the range of valid elements, this | |||||
will return zero. | |||||
If you're certain that the index will always be a valid element, you | |||||
can call getUnchecked() instead, which is faster. | |||||
@param index the index of the element being requested (0 is the first element in the set) | |||||
@see getUnchecked, getFirst, getLast | |||||
*/ | |||||
inline ElementType operator[] (const int index) const noexcept | |||||
{ | |||||
return data [index]; | |||||
} | |||||
/** Returns one of the elements in the set, without checking the index passed in. | |||||
Unlike the operator[] method, this will try to return an element without | |||||
checking that the index is within the bounds of the set, so should only | |||||
be used when you're confident that it will always be a valid index. | |||||
@param index the index of the element being requested (0 is the first element in the set) | |||||
@see operator[], getFirst, getLast | |||||
*/ | |||||
inline ElementType getUnchecked (const int index) const noexcept | |||||
{ | |||||
return data.getUnchecked (index); | |||||
} | |||||
/** Returns a direct reference to one of the elements in the set, without checking the index passed in. | |||||
This is like getUnchecked, but returns a direct reference to the element, so that | |||||
you can alter it directly. Obviously this can be dangerous, so only use it when | |||||
absolutely necessary. | |||||
@param index the index of the element being requested (0 is the first element in the array) | |||||
*/ | |||||
inline ElementType& getReference (const int index) const noexcept | |||||
{ | |||||
return data.getReference (index); | |||||
} | |||||
/** Returns the first element in the set, or 0 if the set is empty. | |||||
@see operator[], getUnchecked, getLast | |||||
*/ | |||||
inline ElementType getFirst() const noexcept | |||||
{ | |||||
return data.getFirst(); | |||||
} | |||||
/** Returns the last element in the set, or 0 if the set is empty. | |||||
@see operator[], getUnchecked, getFirst | |||||
*/ | |||||
inline ElementType getLast() const noexcept | |||||
{ | |||||
return data.getLast(); | |||||
} | |||||
//============================================================================== | |||||
/** Returns a pointer to the first element in the set. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ElementType* begin() const noexcept | |||||
{ | |||||
return data.begin(); | |||||
} | |||||
/** Returns a pointer to the element which follows the last element in the set. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ElementType* end() const noexcept | |||||
{ | |||||
return data.end(); | |||||
} | |||||
//============================================================================== | |||||
/** Finds the index of the first element which matches the value passed in. | |||||
This will search the set for the given object, and return the index | |||||
of its first occurrence. If the object isn't found, the method will return -1. | |||||
@param elementToLookFor the value or object to look for | |||||
@returns the index of the object, or -1 if it's not found | |||||
*/ | |||||
int indexOf (const ElementType& elementToLookFor) const noexcept | |||||
{ | |||||
int s = 0; | |||||
int e = data.size(); | |||||
for (;;) | |||||
{ | |||||
if (s >= e) | |||||
return -1; | |||||
if (elementToLookFor == data.getReference (s)) | |||||
return s; | |||||
const int halfway = (s + e) / 2; | |||||
if (halfway == s) | |||||
return -1; | |||||
if (elementToLookFor < data.getReference (halfway)) | |||||
e = halfway; | |||||
else | |||||
s = halfway; | |||||
} | |||||
} | |||||
/** Returns true if the set contains at least one occurrence of an object. | |||||
@param elementToLookFor the value or object to look for | |||||
@returns true if the item is found | |||||
*/ | |||||
bool contains (const ElementType& elementToLookFor) const noexcept | |||||
{ | |||||
return indexOf (elementToLookFor) >= 0; | |||||
} | |||||
//============================================================================== | |||||
/** Adds a new element to the set, (as long as it's not already in there). | |||||
Note that if a matching element already exists, the new value will be assigned | |||||
to the existing one using operator=, so that if there are any differences between | |||||
the objects which were not recognised by the object's operator==, then the | |||||
set will always contain a copy of the most recently added one. | |||||
@param newElement the new object to add to the set | |||||
@returns true if the value was added, or false if it already existed | |||||
@see set, insert, addIfNotAlreadyThere, addSorted, addSet, addArray | |||||
*/ | |||||
bool add (const ElementType& newElement) noexcept | |||||
{ | |||||
int s = 0; | |||||
int e = data.size(); | |||||
while (s < e) | |||||
{ | |||||
ElementType& elem = data.getReference (s); | |||||
if (newElement == elem) | |||||
{ | |||||
elem = newElement; // force an update in case operator== permits differences. | |||||
return false; | |||||
} | |||||
const int halfway = (s + e) / 2; | |||||
const bool isBeforeHalfway = (newElement < data.getReference (halfway)); | |||||
if (halfway == s) | |||||
{ | |||||
if (! isBeforeHalfway) | |||||
++s; | |||||
break; | |||||
} | |||||
if (isBeforeHalfway) | |||||
e = halfway; | |||||
else | |||||
s = halfway; | |||||
} | |||||
data.insert (s, newElement); | |||||
return true; | |||||
} | |||||
/** Adds elements from an array to this set. | |||||
@param elementsToAdd the array of elements to add | |||||
@param numElementsToAdd how many elements are in this other array | |||||
@see add | |||||
*/ | |||||
void addArray (const ElementType* elementsToAdd, | |||||
int numElementsToAdd) noexcept | |||||
{ | |||||
while (--numElementsToAdd >= 0) | |||||
add (*elementsToAdd++); | |||||
} | |||||
/** Adds elements from another set to this one. | |||||
@param setToAddFrom the set from which to copy the elements | |||||
@param startIndex the first element of the other set to start copying from | |||||
@param numElementsToAdd how many elements to add from the other set. If this | |||||
value is negative or greater than the number of available elements, | |||||
all available elements will be copied. | |||||
@see add | |||||
*/ | |||||
template <class OtherSetType> | |||||
void addSet (const OtherSetType& setToAddFrom, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1) noexcept | |||||
{ | |||||
jassert (this != &setToAddFrom); | |||||
if (this != &setToAddFrom) | |||||
{ | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) | |||||
numElementsToAdd = setToAddFrom.size() - startIndex; | |||||
if (numElementsToAdd > 0) | |||||
addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Removes an element from the set. | |||||
This will remove the element at a given index. | |||||
If the index passed in is out-of-range, nothing will happen. | |||||
@param indexToRemove the index of the element to remove | |||||
@returns the element that has been removed | |||||
@see removeValue, removeRange | |||||
*/ | |||||
ElementType remove (const int indexToRemove) noexcept | |||||
{ | |||||
return data.removeAndReturn (indexToRemove); | |||||
} | |||||
/** Removes an item from the set. | |||||
This will remove the given element from the set, if it's there. | |||||
@param valueToRemove the object to try to remove | |||||
@see remove, removeRange | |||||
*/ | |||||
void removeValue (const ElementType valueToRemove) noexcept | |||||
{ | |||||
data.remove (indexOf (valueToRemove)); | |||||
} | |||||
/** Removes any elements which are also in another set. | |||||
@param otherSet the other set in which to look for elements to remove | |||||
@see removeValuesNotIn, remove, removeValue, removeRange | |||||
*/ | |||||
template <class OtherSetType> | |||||
void removeValuesIn (const OtherSetType& otherSet) noexcept | |||||
{ | |||||
if (this == &otherSet) | |||||
{ | |||||
clear(); | |||||
} | |||||
else if (otherSet.size() > 0) | |||||
{ | |||||
for (int i = data.size(); --i >= 0;) | |||||
if (otherSet.contains (data.getReference (i))) | |||||
remove (i); | |||||
} | |||||
} | |||||
/** Removes any elements which are not found in another set. | |||||
Only elements which occur in this other set will be retained. | |||||
@param otherSet the set in which to look for elements NOT to remove | |||||
@see removeValuesIn, remove, removeValue, removeRange | |||||
*/ | |||||
template <class OtherSetType> | |||||
void removeValuesNotIn (const OtherSetType& otherSet) noexcept | |||||
{ | |||||
if (this != &otherSet) | |||||
{ | |||||
if (otherSet.size() <= 0) | |||||
{ | |||||
clear(); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = data.size(); --i >= 0;) | |||||
if (! otherSet.contains (data.getReference (i))) | |||||
remove (i); | |||||
} | |||||
} | |||||
} | |||||
/** This swaps the contents of this array with those of another array. | |||||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||||
because it just swaps their internal pointers. | |||||
*/ | |||||
template <class OtherSetType> | |||||
void swapWith (OtherSetType& otherSet) noexcept | |||||
{ | |||||
data.swapWith (otherSet.data); | |||||
} | |||||
//============================================================================== | |||||
/** Reduces the amount of storage being used by the set. | |||||
Sets typically allocate slightly more storage than they need, and after | |||||
removing elements, they may have quite a lot of unused space allocated. | |||||
This method will reduce the amount of allocated storage to a minimum. | |||||
*/ | |||||
void minimiseStorageOverheads() noexcept | |||||
{ | |||||
data.minimiseStorageOverheads(); | |||||
} | |||||
/** Increases the set's internal storage to hold a minimum number of elements. | |||||
Calling this before adding a large known number of elements means that | |||||
the set won't have to keep dynamically resizing itself as the elements | |||||
are added, and it'll therefore be more efficient. | |||||
*/ | |||||
void ensureStorageAllocated (const int minNumElements) | |||||
{ | |||||
data.ensureStorageAllocated (minNumElements); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
Array<ElementType> data; | |||||
}; | |||||
#if JUCE_MSVC | |||||
#pragma warning (pop) | |||||
#endif | |||||
#endif // JUCE_SORTEDSET_H_INCLUDED |
@@ -0,0 +1,987 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_FILE_H_INCLUDED | |||||
#define JUCE_FILE_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Represents a local file or directory. | |||||
This class encapsulates the absolute pathname of a file or directory, and | |||||
has methods for finding out about the file and changing its properties. | |||||
To read or write to the file, there are methods for returning an input or | |||||
output stream. | |||||
@see FileInputStream, FileOutputStream | |||||
*/ | |||||
class JUCE_API File | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an (invalid) file object. | |||||
The file is initially set to an empty path, so getFullPathName() will return | |||||
an empty string. | |||||
You can use its operator= method to point it at a proper file. | |||||
*/ | |||||
File() noexcept {} | |||||
/** Creates a file from an absolute path. | |||||
If the path supplied is a relative path, it is taken to be relative | |||||
to the current working directory (see File::getCurrentWorkingDirectory()), | |||||
but this isn't a recommended way of creating a file, because you | |||||
never know what the CWD is going to be. | |||||
On the Mac/Linux, the path can include "~" notation for referring to | |||||
user home directories. | |||||
*/ | |||||
File (const String& absolutePath); | |||||
/** Creates a copy of another file object. */ | |||||
File (const File&); | |||||
/** Destructor. */ | |||||
~File() noexcept {} | |||||
/** Sets the file based on an absolute pathname. | |||||
If the path supplied is a relative path, it is taken to be relative | |||||
to the current working directory (see File::getCurrentWorkingDirectory()), | |||||
but this isn't a recommended way of creating a file, because you | |||||
never know what the CWD is going to be. | |||||
On the Mac/Linux, the path can include "~" notation for referring to | |||||
user home directories. | |||||
*/ | |||||
File& operator= (const String& newAbsolutePath); | |||||
/** Copies from another file object. */ | |||||
File& operator= (const File& otherFile); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
File (File&&) noexcept; | |||||
File& operator= (File&&) noexcept; | |||||
#endif | |||||
//============================================================================== | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
/** This static constant is used for referring to an 'invalid' file. | |||||
Bear in mind that you should avoid this kind of static variable, and always prefer | |||||
to use File() or {} if you need a default-constructed File object. | |||||
*/ | |||||
static const File nonexistent; | |||||
#endif | |||||
//============================================================================== | |||||
/** Checks whether the file actually exists. | |||||
@returns true if the file exists, either as a file or a directory. | |||||
@see existsAsFile, isDirectory | |||||
*/ | |||||
bool exists() const; | |||||
/** Checks whether the file exists and is a file rather than a directory. | |||||
@returns true only if this is a real file, false if it's a directory | |||||
or doesn't exist | |||||
@see exists, isDirectory | |||||
*/ | |||||
bool existsAsFile() const; | |||||
/** Checks whether the file is a directory that exists. | |||||
@returns true only if the file is a directory which actually exists, so | |||||
false if it's a file or doesn't exist at all | |||||
@see exists, existsAsFile | |||||
*/ | |||||
bool isDirectory() const; | |||||
/** Returns the size of the file in bytes. | |||||
@returns the number of bytes in the file, or 0 if it doesn't exist. | |||||
*/ | |||||
int64 getSize() const; | |||||
/** Utility function to convert a file size in bytes to a neat string description. | |||||
So for example 100 would return "100 bytes", 2000 would return "2 KB", | |||||
2000000 would produce "2 MB", etc. | |||||
*/ | |||||
static String descriptionOfSizeInBytes (int64 bytes); | |||||
//============================================================================== | |||||
/** Returns the complete, absolute path of this file. | |||||
This includes the filename and all its parent folders. On Windows it'll | |||||
also include the drive letter prefix; on Mac or Linux it'll be a complete | |||||
path starting from the root folder. | |||||
If you just want the file's name, you should use getFileName() or | |||||
getFileNameWithoutExtension(). | |||||
@see getFileName, getRelativePathFrom | |||||
*/ | |||||
const String& getFullPathName() const noexcept { return fullPath; } | |||||
/** Returns the last section of the pathname. | |||||
Returns just the final part of the path - e.g. if the whole path | |||||
is "/moose/fish/foo.txt" this will return "foo.txt". | |||||
For a directory, it returns the final part of the path - e.g. for the | |||||
directory "/moose/fish" it'll return "fish". | |||||
If the filename begins with a dot, it'll return the whole filename, e.g. for | |||||
"/moose/.fish", it'll return ".fish" | |||||
@see getFullPathName, getFileNameWithoutExtension | |||||
*/ | |||||
String getFileName() const; | |||||
/** Creates a relative path that refers to a file relatively to a given directory. | |||||
e.g. File ("/moose/foo.txt").getRelativePathFrom (File ("/moose/fish/haddock")) | |||||
would return "../../foo.txt". | |||||
If it's not possible to navigate from one file to the other, an absolute | |||||
path is returned. If the paths are invalid, an empty string may also be | |||||
returned. | |||||
@param directoryToBeRelativeTo the directory which the resultant string will | |||||
be relative to. If this is actually a file rather than | |||||
a directory, its parent directory will be used instead. | |||||
If it doesn't exist, it's assumed to be a directory. | |||||
@see getChildFile, isAbsolutePath | |||||
*/ | |||||
String getRelativePathFrom (const File& directoryToBeRelativeTo) const; | |||||
//============================================================================== | |||||
/** Returns the file's extension. | |||||
Returns the file extension of this file, also including the dot. | |||||
e.g. "/moose/fish/foo.txt" would return ".txt" | |||||
@see hasFileExtension, withFileExtension, getFileNameWithoutExtension | |||||
*/ | |||||
String getFileExtension() const; | |||||
/** Checks whether the file has a given extension. | |||||
@param extensionToTest the extension to look for - it doesn't matter whether or | |||||
not this string has a dot at the start, so ".wav" and "wav" | |||||
will have the same effect. To compare with multiple extensions, this | |||||
parameter can contain multiple strings, separated by semi-colons - | |||||
so, for example: hasFileExtension (".jpeg;png;gif") would return | |||||
true if the file has any of those three extensions. | |||||
@see getFileExtension, withFileExtension, getFileNameWithoutExtension | |||||
*/ | |||||
bool hasFileExtension (StringRef extensionToTest) const; | |||||
/** Returns a version of this file with a different file extension. | |||||
e.g. File ("/moose/fish/foo.txt").withFileExtension ("html") returns "/moose/fish/foo.html" | |||||
@param newExtension the new extension, either with or without a dot at the start (this | |||||
doesn't make any difference). To get remove a file's extension altogether, | |||||
pass an empty string into this function. | |||||
@see getFileName, getFileExtension, hasFileExtension, getFileNameWithoutExtension | |||||
*/ | |||||
File withFileExtension (StringRef newExtension) const; | |||||
/** Returns the last part of the filename, without its file extension. | |||||
e.g. for "/moose/fish/foo.txt" this will return "foo". | |||||
@see getFileName, getFileExtension, hasFileExtension, withFileExtension | |||||
*/ | |||||
String getFileNameWithoutExtension() const; | |||||
//============================================================================== | |||||
/** Returns a 32-bit hash-code that identifies this file. | |||||
This is based on the filename. Obviously it's possible, although unlikely, that | |||||
two files will have the same hash-code. | |||||
*/ | |||||
int hashCode() const; | |||||
/** Returns a 64-bit hash-code that identifies this file. | |||||
This is based on the filename. Obviously it's possible, although unlikely, that | |||||
two files will have the same hash-code. | |||||
*/ | |||||
int64 hashCode64() const; | |||||
//============================================================================== | |||||
/** Returns a file that represents a relative (or absolute) sub-path of the current one. | |||||
This will find a child file or directory of the current object. | |||||
e.g. | |||||
File ("/moose/fish").getChildFile ("foo.txt") will produce "/moose/fish/foo.txt". | |||||
File ("/moose/fish").getChildFile ("haddock/foo.txt") will produce "/moose/fish/haddock/foo.txt". | |||||
File ("/moose/fish").getChildFile ("../foo.txt") will produce "/moose/foo.txt". | |||||
If the string is actually an absolute path, it will be treated as such, e.g. | |||||
File ("/moose/fish").getChildFile ("/foo.txt") will produce "/foo.txt" | |||||
@see getSiblingFile, getParentDirectory, getRelativePathFrom, isAChildOf | |||||
*/ | |||||
File getChildFile (StringRef relativeOrAbsolutePath) const; | |||||
/** Returns a file which is in the same directory as this one. | |||||
This is equivalent to getParentDirectory().getChildFile (name). | |||||
@see getChildFile, getParentDirectory | |||||
*/ | |||||
File getSiblingFile (StringRef siblingFileName) const; | |||||
//============================================================================== | |||||
/** Returns the directory that contains this file or directory. | |||||
e.g. for "/moose/fish/foo.txt" this will return "/moose/fish". | |||||
*/ | |||||
File getParentDirectory() const; | |||||
/** Checks whether a file is somewhere inside a directory. | |||||
Returns true if this file is somewhere inside a subdirectory of the directory | |||||
that is passed in. Neither file actually has to exist, because the function | |||||
just checks the paths for similarities. | |||||
e.g. File ("/moose/fish/foo.txt").isAChildOf ("/moose") is true. | |||||
File ("/moose/fish/foo.txt").isAChildOf ("/moose/fish") is also true. | |||||
*/ | |||||
bool isAChildOf (const File& potentialParentDirectory) const; | |||||
//============================================================================== | |||||
/** Chooses a filename relative to this one that doesn't already exist. | |||||
If this file is a directory, this will return a child file of this | |||||
directory that doesn't exist, by adding numbers to a prefix and suffix until | |||||
it finds one that isn't already there. | |||||
If the prefix + the suffix doesn't exist, it won't bother adding a number. | |||||
e.g. File ("/moose/fish").getNonexistentChildFile ("foo", ".txt", true) might | |||||
return "/moose/fish/foo(2).txt" if there's already a file called "foo.txt". | |||||
@param prefix the string to use for the filename before the number | |||||
@param suffix the string to add to the filename after the number | |||||
@param putNumbersInBrackets if true, this will create filenames in the | |||||
format "prefix(number)suffix", if false, it will leave the | |||||
brackets out. | |||||
*/ | |||||
File getNonexistentChildFile (const String& prefix, | |||||
const String& suffix, | |||||
bool putNumbersInBrackets = true) const; | |||||
/** Chooses a filename for a sibling file to this one that doesn't already exist. | |||||
If this file doesn't exist, this will just return itself, otherwise it | |||||
will return an appropriate sibling that doesn't exist, e.g. if a file | |||||
"/moose/fish/foo.txt" exists, this might return "/moose/fish/foo(2).txt". | |||||
@param putNumbersInBrackets whether to add brackets around the numbers that | |||||
get appended to the new filename. | |||||
*/ | |||||
File getNonexistentSibling (bool putNumbersInBrackets = true) const; | |||||
//============================================================================== | |||||
/** Compares the pathnames for two files. */ | |||||
bool operator== (const File&) const; | |||||
/** Compares the pathnames for two files. */ | |||||
bool operator!= (const File&) const; | |||||
/** Compares the pathnames for two files. */ | |||||
bool operator< (const File&) const; | |||||
/** Compares the pathnames for two files. */ | |||||
bool operator> (const File&) const; | |||||
//============================================================================== | |||||
/** Checks whether a file can be created or written to. | |||||
@returns true if it's possible to create and write to this file. If the file | |||||
doesn't already exist, this will check its parent directory to | |||||
see if writing is allowed. | |||||
@see setReadOnly | |||||
*/ | |||||
bool hasWriteAccess() const; | |||||
/** Changes the write-permission of a file or directory. | |||||
@param shouldBeReadOnly whether to add or remove write-permission | |||||
@param applyRecursively if the file is a directory and this is true, it will | |||||
recurse through all the subfolders changing the permissions | |||||
of all files | |||||
@returns true if it manages to change the file's permissions. | |||||
@see hasWriteAccess | |||||
*/ | |||||
bool setReadOnly (bool shouldBeReadOnly, | |||||
bool applyRecursively = false) const; | |||||
/** Changes the execute-permissions of a file. | |||||
@param shouldBeExecutable whether to add or remove execute-permission | |||||
@returns true if it manages to change the file's permissions. | |||||
*/ | |||||
bool setExecutePermission (bool shouldBeExecutable) const; | |||||
/** Returns true if this file is a hidden or system file. | |||||
The criteria for deciding whether a file is hidden are platform-dependent. | |||||
*/ | |||||
bool isHidden() const; | |||||
/** Returns a unique identifier for the file, if one is available. | |||||
Depending on the OS and file-system, this may be a unix inode number or | |||||
a win32 file identifier, or 0 if it fails to find one. The number will | |||||
be unique on the filesystem, but not globally. | |||||
*/ | |||||
uint64 getFileIdentifier() const; | |||||
/** If possible, this will try to create a version string for the given file. | |||||
The OS may be able to look at the file and give a version for it - e.g. with | |||||
executables, bundles, dlls, etc. If no version is available, this will | |||||
return an empty string. | |||||
*/ | |||||
String getVersion() const; | |||||
//============================================================================== | |||||
/** Creates an empty file if it doesn't already exist. | |||||
If the file that this object refers to doesn't exist, this will create a file | |||||
of zero size. | |||||
If it already exists or is a directory, this method will do nothing. | |||||
If the parent directories of the File do not exist then this method will | |||||
recursively create the parent directories. | |||||
@returns a result to indicate whether the file was created successfully, | |||||
or an error message if it failed. | |||||
@see createDirectory | |||||
*/ | |||||
Result create() const; | |||||
/** Creates a new directory for this filename. | |||||
This will try to create the file as a directory, and will also create | |||||
any parent directories it needs in order to complete the operation. | |||||
@returns a result to indicate whether the directory was created successfully, or | |||||
an error message if it failed. | |||||
@see create | |||||
*/ | |||||
Result createDirectory() const; | |||||
/** Deletes a file. | |||||
If this file is actually a directory, it may not be deleted correctly if it | |||||
contains files. See deleteRecursively() as a better way of deleting directories. | |||||
@returns true if the file has been successfully deleted (or if it didn't exist to | |||||
begin with). | |||||
@see deleteRecursively | |||||
*/ | |||||
bool deleteFile() const; | |||||
/** Deletes a file or directory and all its subdirectories. | |||||
If this file is a directory, this will try to delete it and all its subfolders. If | |||||
it's just a file, it will just try to delete the file. | |||||
@returns true if the file and all its subfolders have been successfully deleted | |||||
(or if it didn't exist to begin with). | |||||
@see deleteFile | |||||
*/ | |||||
bool deleteRecursively() const; | |||||
/** Moves this file or folder to the trash. | |||||
@returns true if the operation succeeded. It could fail if the trash is full, or | |||||
if the file is write-protected, so you should check the return value | |||||
and act appropriately. | |||||
*/ | |||||
bool moveToTrash() const; | |||||
/** Moves or renames a file. | |||||
Tries to move a file to a different location. | |||||
If the target file already exists, this will attempt to delete it first, and | |||||
will fail if this can't be done. | |||||
Note that the destination file isn't the directory to put it in, it's the actual | |||||
filename that you want the new file to have. | |||||
Also note that on some OSes (e.g. Windows), moving files between different | |||||
volumes may not be possible. | |||||
@returns true if the operation succeeds | |||||
*/ | |||||
bool moveFileTo (const File& targetLocation) const; | |||||
/** Copies a file. | |||||
Tries to copy a file to a different location. | |||||
If the target file already exists, this will attempt to delete it first, and | |||||
will fail if this can't be done. | |||||
@returns true if the operation succeeds | |||||
*/ | |||||
bool copyFileTo (const File& targetLocation) const; | |||||
/** Replaces a file. | |||||
Replace the file in the given location, assuming the replaced files identity. | |||||
Depending on the file system this will preserve file attributes such as | |||||
creation date, short file name, etc. | |||||
If replacement succeeds the original file is deleted. | |||||
@returns true if the operation succeeds | |||||
*/ | |||||
bool replaceFileIn (const File& targetLocation) const; | |||||
/** Copies a directory. | |||||
Tries to copy an entire directory, recursively. | |||||
If this file isn't a directory or if any target files can't be created, this | |||||
will return false. | |||||
@param newDirectory the directory that this one should be copied to. Note that this | |||||
is the name of the actual directory to create, not the directory | |||||
into which the new one should be placed, so there must be enough | |||||
write privileges to create it if it doesn't exist. Any files inside | |||||
it will be overwritten by similarly named ones that are copied. | |||||
*/ | |||||
bool copyDirectoryTo (const File& newDirectory) const; | |||||
//============================================================================== | |||||
/** Used in file searching, to specify whether to return files, directories, or both. | |||||
*/ | |||||
enum TypesOfFileToFind | |||||
{ | |||||
findDirectories = 1, /**< Use this flag to indicate that you want to find directories. */ | |||||
findFiles = 2, /**< Use this flag to indicate that you want to find files. */ | |||||
findFilesAndDirectories = 3, /**< Use this flag to indicate that you want to find both files and directories. */ | |||||
ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */ | |||||
}; | |||||
/** Searches inside a directory for files matching a wildcard pattern. | |||||
Assuming that this file is a directory, this method will search it | |||||
for either files or subdirectories whose names match a filename pattern. | |||||
@param results an array to which File objects will be added for the | |||||
files that the search comes up with | |||||
@param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to | |||||
return files, directories, or both. If the ignoreHiddenFiles flag | |||||
is also added to this value, hidden files won't be returned | |||||
@param searchRecursively if true, all subdirectories will be recursed into to do | |||||
an exhaustive search | |||||
@param wildCardPattern the filename pattern to search for, e.g. "*.txt" | |||||
@returns the number of results that have been found | |||||
@see getNumberOfChildFiles, DirectoryIterator | |||||
*/ | |||||
int findChildFiles (Array<File>& results, | |||||
int whatToLookFor, | |||||
bool searchRecursively, | |||||
const String& wildCardPattern = "*") const; | |||||
/** Searches inside a directory and counts how many files match a wildcard pattern. | |||||
Assuming that this file is a directory, this method will search it | |||||
for either files or subdirectories whose names match a filename pattern, | |||||
and will return the number of matches found. | |||||
This isn't a recursive call, and will only search this directory, not | |||||
its children. | |||||
@param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to | |||||
count files, directories, or both. If the ignoreHiddenFiles flag | |||||
is also added to this value, hidden files won't be counted | |||||
@param wildCardPattern the filename pattern to search for, e.g. "*.txt" | |||||
@returns the number of matches found | |||||
@see findChildFiles, DirectoryIterator | |||||
*/ | |||||
int getNumberOfChildFiles (int whatToLookFor, | |||||
const String& wildCardPattern = "*") const; | |||||
/** Returns true if this file is a directory that contains one or more subdirectories. | |||||
@see isDirectory, findChildFiles | |||||
*/ | |||||
bool containsSubDirectories() const; | |||||
//============================================================================== | |||||
/** Creates a stream to read from this file. | |||||
@returns a stream that will read from this file (initially positioned at the | |||||
start of the file), or nullptr if the file can't be opened for some reason | |||||
@see createOutputStream, loadFileAsData | |||||
*/ | |||||
FileInputStream* createInputStream() const; | |||||
/** Creates a stream to write to this file. | |||||
If the file exists, the stream that is returned will be positioned ready for | |||||
writing at the end of the file, so you might want to use deleteFile() first | |||||
to write to an empty file. | |||||
@returns a stream that will write to this file (initially positioned at the | |||||
end of the file), or nullptr if the file can't be opened for some reason | |||||
@see createInputStream, appendData, appendText | |||||
*/ | |||||
FileOutputStream* createOutputStream (size_t bufferSize = 0x8000) const; | |||||
//============================================================================== | |||||
/** Loads a file's contents into memory as a block of binary data. | |||||
Of course, trying to load a very large file into memory will blow up, so | |||||
it's better to check first. | |||||
@param result the data block to which the file's contents should be appended - note | |||||
that if the memory block might already contain some data, you | |||||
might want to clear it first | |||||
@returns true if the file could all be read into memory | |||||
*/ | |||||
bool loadFileAsData (MemoryBlock& result) const; | |||||
/** Reads a file into memory as a string. | |||||
Attempts to load the entire file as a zero-terminated string. | |||||
This makes use of InputStream::readEntireStreamAsString, which can | |||||
read either UTF-16 or UTF-8 file formats. | |||||
*/ | |||||
String loadFileAsString() const; | |||||
/** Reads the contents of this file as text and splits it into lines, which are | |||||
appended to the given StringArray. | |||||
*/ | |||||
void readLines (StringArray& destLines) const; | |||||
//============================================================================== | |||||
/** Appends a block of binary data to the end of the file. | |||||
This will try to write the given buffer to the end of the file. | |||||
@returns false if it can't write to the file for some reason | |||||
*/ | |||||
bool appendData (const void* dataToAppend, | |||||
size_t numberOfBytes) const; | |||||
/** Replaces this file's contents with a given block of data. | |||||
This will delete the file and replace it with the given data. | |||||
A nice feature of this method is that it's safe - instead of deleting | |||||
the file first and then re-writing it, it creates a new temporary file, | |||||
writes the data to that, and then moves the new file to replace the existing | |||||
file. This means that if the power gets pulled out or something crashes, | |||||
you're a lot less likely to end up with a corrupted or unfinished file.. | |||||
Returns true if the operation succeeds, or false if it fails. | |||||
@see appendText | |||||
*/ | |||||
bool replaceWithData (const void* dataToWrite, | |||||
size_t numberOfBytes) const; | |||||
/** Appends a string to the end of the file. | |||||
This will try to append a text string to the file, as either 16-bit unicode | |||||
or 8-bit characters in the default system encoding. | |||||
It can also write the 'ff fe' unicode header bytes before the text to indicate | |||||
the endianness of the file. | |||||
Any single \\n characters in the string are replaced with \\r\\n before it is written. | |||||
@see replaceWithText | |||||
*/ | |||||
bool appendText (const String& textToAppend, | |||||
bool asUnicode = false, | |||||
bool writeUnicodeHeaderBytes = false) const; | |||||
/** Replaces this file's contents with a given text string. | |||||
This will delete the file and replace it with the given text. | |||||
A nice feature of this method is that it's safe - instead of deleting | |||||
the file first and then re-writing it, it creates a new temporary file, | |||||
writes the text to that, and then moves the new file to replace the existing | |||||
file. This means that if the power gets pulled out or something crashes, | |||||
you're a lot less likely to end up with an empty file.. | |||||
For an explanation of the parameters here, see the appendText() method. | |||||
Returns true if the operation succeeds, or false if it fails. | |||||
@see appendText | |||||
*/ | |||||
bool replaceWithText (const String& textToWrite, | |||||
bool asUnicode = false, | |||||
bool writeUnicodeHeaderBytes = false) const; | |||||
/** Attempts to scan the contents of this file and compare it to another file, returning | |||||
true if this is possible and they match byte-for-byte. | |||||
*/ | |||||
bool hasIdenticalContentTo (const File& other) const; | |||||
//============================================================================== | |||||
/** Creates a set of files to represent each file root. | |||||
e.g. on Windows this will create files for "c:\", "d:\" etc according | |||||
to which ones are available. On the Mac/Linux, this will probably | |||||
just add a single entry for "/". | |||||
*/ | |||||
static void findFileSystemRoots (Array<File>& results); | |||||
/** Finds the name of the drive on which this file lives. | |||||
@returns the volume label of the drive, or an empty string if this isn't possible | |||||
*/ | |||||
String getVolumeLabel() const; | |||||
/** Returns the serial number of the volume on which this file lives. | |||||
@returns the serial number, or zero if there's a problem doing this | |||||
*/ | |||||
int getVolumeSerialNumber() const; | |||||
/** Returns the number of bytes free on the drive that this file lives on. | |||||
@returns the number of bytes free, or 0 if there's a problem finding this out | |||||
@see getVolumeTotalSize | |||||
*/ | |||||
int64 getBytesFreeOnVolume() const; | |||||
/** Returns the total size of the drive that contains this file. | |||||
@returns the total number of bytes that the volume can hold | |||||
@see getBytesFreeOnVolume | |||||
*/ | |||||
int64 getVolumeTotalSize() const; | |||||
/** Returns true if this file is on a CD or DVD drive. */ | |||||
bool isOnCDRomDrive() const; | |||||
/** Returns true if this file is on a hard disk. | |||||
This will fail if it's a network drive, but will still be true for | |||||
removable hard-disks. | |||||
*/ | |||||
bool isOnHardDisk() const; | |||||
/** Returns true if this file is on a removable disk drive. | |||||
This might be a usb-drive, a CD-rom, or maybe a network drive. | |||||
*/ | |||||
bool isOnRemovableDrive() const; | |||||
//============================================================================== | |||||
/** Launches the file as a process. | |||||
- if the file is executable, this will run it. | |||||
- if it's a document of some kind, it will launch the document with its | |||||
default viewer application. | |||||
- if it's a folder, it will be opened in Explorer, Finder, or equivalent. | |||||
@see revealToUser | |||||
*/ | |||||
bool startAsProcess (const String& parameters = String()) const; | |||||
/** Opens Finder, Explorer, or whatever the OS uses, to show the user this file's location. | |||||
@see startAsProcess | |||||
*/ | |||||
void revealToUser() const; | |||||
//============================================================================== | |||||
/** A set of types of location that can be passed to the getSpecialLocation() method. | |||||
*/ | |||||
enum SpecialLocationType | |||||
{ | |||||
/** The user's home folder. This is the same as using File ("~"). */ | |||||
userHomeDirectory, | |||||
/** The user's default documents folder. On Windows, this might be the user's | |||||
"My Documents" folder. On the Mac it'll be their "Documents" folder. Linux | |||||
doesn't tend to have one of these, so it might just return their home folder. | |||||
*/ | |||||
userDocumentsDirectory, | |||||
/** The folder that contains the user's desktop objects. */ | |||||
userDesktopDirectory, | |||||
/** The most likely place where a user might store their music files. */ | |||||
userMusicDirectory, | |||||
/** The most likely place where a user might store their movie files. */ | |||||
userMoviesDirectory, | |||||
/** The most likely place where a user might store their picture files. */ | |||||
userPicturesDirectory, | |||||
/** The folder in which applications store their persistent user-specific settings. | |||||
On Windows, this might be "\Documents and Settings\username\Application Data". | |||||
On the Mac, it might be "~/Library". If you're going to store your settings in here, | |||||
always create your own sub-folder to put them in, to avoid making a mess. | |||||
*/ | |||||
userApplicationDataDirectory, | |||||
/** An equivalent of the userApplicationDataDirectory folder that is shared by all users | |||||
of the computer, rather than just the current user. | |||||
On the Mac it'll be "/Library", on Windows, it could be something like | |||||
"\Documents and Settings\All Users\Application Data". | |||||
Depending on the setup, this folder may be read-only. | |||||
*/ | |||||
commonApplicationDataDirectory, | |||||
/** A place to put documents which are shared by all users of the machine. | |||||
On Windows this may be somewhere like "C:\Users\Public\Documents", on OSX it | |||||
will be something like "/Users/Shared". Other OSes may have no such concept | |||||
though, so be careful. | |||||
*/ | |||||
commonDocumentsDirectory, | |||||
/** The folder that should be used for temporary files. | |||||
Always delete them when you're finished, to keep the user's computer tidy! | |||||
*/ | |||||
tempDirectory, | |||||
/** Returns this application's executable file. | |||||
If running as a plug-in or DLL, this will (where possible) be the DLL rather than the | |||||
host app. | |||||
On the mac this will return the unix binary, not the package folder - see | |||||
currentApplicationFile for that. | |||||
See also invokedExecutableFile, which is similar, but if the exe was launched from a | |||||
file link, invokedExecutableFile will return the name of the link. | |||||
*/ | |||||
currentExecutableFile, | |||||
/** Returns this application's location. | |||||
If running as a plug-in or DLL, this will (where possible) be the DLL rather than the | |||||
host app. | |||||
On the mac this will return the package folder (if it's in one), not the unix binary | |||||
that's inside it - compare with currentExecutableFile. | |||||
*/ | |||||
currentApplicationFile, | |||||
/** Returns the file that was invoked to launch this executable. | |||||
This may differ from currentExecutableFile if the app was started from e.g. a link - this | |||||
will return the name of the link that was used, whereas currentExecutableFile will return | |||||
the actual location of the target executable. | |||||
*/ | |||||
invokedExecutableFile, | |||||
/** In a plugin, this will return the path of the host executable. */ | |||||
hostApplicationPath, | |||||
#if JUCE_WINDOWS | |||||
/** On a Windows machine, returns the location of the Windows/System32 folder. */ | |||||
windowsSystemDirectory, | |||||
#endif | |||||
/** The directory in which applications normally get installed. | |||||
So on windows, this would be something like "c:\program files", on the | |||||
Mac "/Applications", or "/usr" on linux. | |||||
*/ | |||||
globalApplicationsDirectory | |||||
}; | |||||
/** Finds the location of a special type of file or directory, such as a home folder or | |||||
documents folder. | |||||
@see SpecialLocationType | |||||
*/ | |||||
static File JUCE_CALLTYPE getSpecialLocation (const SpecialLocationType type); | |||||
//============================================================================== | |||||
/** Returns a temporary file in the system's temp directory. | |||||
This will try to return the name of a non-existent temp file. | |||||
To get the temp folder, you can use getSpecialLocation (File::tempDirectory). | |||||
*/ | |||||
static File createTempFile (StringRef fileNameEnding); | |||||
//============================================================================== | |||||
/** Returns the current working directory. | |||||
@see setAsCurrentWorkingDirectory | |||||
*/ | |||||
static File getCurrentWorkingDirectory(); | |||||
/** Sets the current working directory to be this file. | |||||
For this to work the file must point to a valid directory. | |||||
@returns true if the current directory has been changed. | |||||
@see getCurrentWorkingDirectory | |||||
*/ | |||||
bool setAsCurrentWorkingDirectory() const; | |||||
//============================================================================== | |||||
/** The system-specific file separator character. | |||||
On Windows, this will be '\', on Mac/Linux, it'll be '/' | |||||
*/ | |||||
static const juce_wchar separator; | |||||
/** The system-specific file separator character, as a string. | |||||
On Windows, this will be '\', on Mac/Linux, it'll be '/' | |||||
*/ | |||||
static const String separatorString; | |||||
//============================================================================== | |||||
/** Returns a version of a filename with any illegal characters removed. | |||||
This will return a copy of the given string after removing characters | |||||
that are not allowed in a legal filename, and possibly shortening the | |||||
string if it's too long. | |||||
Because this will remove slashes, don't use it on an absolute pathname - use | |||||
createLegalPathName() for that. | |||||
@see createLegalPathName | |||||
*/ | |||||
static String createLegalFileName (const String& fileNameToFix); | |||||
/** Returns a version of a path with any illegal characters removed. | |||||
Similar to createLegalFileName(), but this won't remove slashes, so can | |||||
be used on a complete pathname. | |||||
@see createLegalFileName | |||||
*/ | |||||
static String createLegalPathName (const String& pathNameToFix); | |||||
/** Indicates whether filenames are case-sensitive on the current operating system. */ | |||||
static bool areFileNamesCaseSensitive(); | |||||
/** Returns true if the string seems to be a fully-specified absolute path. */ | |||||
static bool isAbsolutePath (StringRef path); | |||||
/** Creates a file that simply contains this string, without doing the sanity-checking | |||||
that the normal constructors do. | |||||
Best to avoid this unless you really know what you're doing. | |||||
*/ | |||||
static File createFileWithoutCheckingPath (const String& absolutePath) noexcept; | |||||
/** Adds a separator character to the end of a path if it doesn't already have one. */ | |||||
static String addTrailingSeparator (const String& path); | |||||
//============================================================================== | |||||
/** Tries to create a symbolic link and returns a boolean to indicate success */ | |||||
bool createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const; | |||||
/** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ | |||||
bool isSymbolicLink() const; | |||||
/** If this file is a link or alias, this returns the file that it points to. | |||||
If the file isn't actually link, it'll just return itself. | |||||
*/ | |||||
File getLinkedTarget() const; | |||||
#if JUCE_WINDOWS | |||||
/** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ | |||||
bool createShortcut (const String& description, const File& linkFileToCreate) const; | |||||
/** Windows ONLY - Returns true if this is a win32 .LNK file. */ | |||||
bool isShortcut() const; | |||||
#endif | |||||
//============================================================================== | |||||
#if JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** OSX ONLY - Finds the OSType of a file from the its resources. */ | |||||
OSType getMacOSType() const; | |||||
/** OSX ONLY - Returns true if this file is actually a bundle. */ | |||||
bool isBundle() const; | |||||
#endif | |||||
#if JUCE_MAC || DOXYGEN | |||||
/** OSX ONLY - Adds this file to the OSX dock */ | |||||
void addToDock() const; | |||||
#endif | |||||
//============================================================================== | |||||
struct NaturalFileComparator | |||||
{ | |||||
NaturalFileComparator (bool shouldPutFoldersFirst) noexcept : foldersFirst (shouldPutFoldersFirst) {} | |||||
int compareElements (const File& firstFile, const File& secondFile) const | |||||
{ | |||||
if (foldersFirst && (firstFile.isDirectory() != secondFile.isDirectory())) | |||||
return firstFile.isDirectory() ? -1 : 1; | |||||
#if NAMES_ARE_CASE_SENSITIVE | |||||
return firstFile.getFullPathName().compareNatural (secondFile.getFullPathName(), true); | |||||
#else | |||||
return firstFile.getFullPathName().compareNatural (secondFile.getFullPathName(), false); | |||||
#endif | |||||
} | |||||
bool foldersFirst; | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
String fullPath; | |||||
static String parseAbsolutePath (const String&); | |||||
String getPathUpToLastSlash() const; | |||||
Result createDirectoryInternal (const String&) const; | |||||
bool copyInternal (const File&) const; | |||||
bool moveInternal (const File&) const; | |||||
bool replaceInternal (const File&) const; | |||||
bool setFileTimesInternal (int64 m, int64 a, int64 c) const; | |||||
void getFileTimesInternal (int64& m, int64& a, int64& c) const; | |||||
bool setFileReadOnlyInternal (bool) const; | |||||
bool setFileExecutableInternal (bool) const; | |||||
}; | |||||
#endif // JUCE_FILE_H_INCLUDED |
@@ -0,0 +1,119 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
static File createTempFile (const File& parentDirectory, String name, | |||||
const String& suffix, const int optionFlags) | |||||
{ | |||||
if ((optionFlags & TemporaryFile::useHiddenFile) != 0) | |||||
name = "." + name; | |||||
return parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & TemporaryFile::putNumbersInBrackets) != 0); | |||||
} | |||||
TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) | |||||
: temporaryFile (createTempFile (File::getSpecialLocation (File::tempDirectory), | |||||
"temp_" + String::toHexString (Random::getSystemRandom().nextInt()), | |||||
suffix, optionFlags)) | |||||
{ | |||||
} | |||||
TemporaryFile::TemporaryFile (const File& target, const int optionFlags) | |||||
: temporaryFile (createTempFile (target.getParentDirectory(), | |||||
target.getFileNameWithoutExtension() | |||||
+ "_temp" + String::toHexString (Random::getSystemRandom().nextInt()), | |||||
target.getFileExtension(), optionFlags)), | |||||
targetFile (target) | |||||
{ | |||||
// If you use this constructor, you need to give it a valid target file! | |||||
jassert (targetFile != File()); | |||||
} | |||||
TemporaryFile::TemporaryFile (const File& target, const File& temporary) | |||||
: temporaryFile (temporary), targetFile (target) | |||||
{ | |||||
} | |||||
TemporaryFile::~TemporaryFile() | |||||
{ | |||||
if (! deleteTemporaryFile()) | |||||
{ | |||||
/* Failed to delete our temporary file! The most likely reason for this would be | |||||
that you've not closed an output stream that was being used to write to file. | |||||
If you find that something beyond your control is changing permissions on | |||||
your temporary files and preventing them from being deleted, you may want to | |||||
call TemporaryFile::deleteTemporaryFile() to detect those error cases and | |||||
handle them appropriately. | |||||
*/ | |||||
jassertfalse; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
bool TemporaryFile::overwriteTargetFileWithTemporary() const | |||||
{ | |||||
// This method only works if you created this object with the constructor | |||||
// that takes a target file! | |||||
jassert (targetFile != File()); | |||||
if (temporaryFile.exists()) | |||||
{ | |||||
// Have a few attempts at overwriting the file before giving up.. | |||||
for (int i = 5; --i >= 0;) | |||||
{ | |||||
if (temporaryFile.replaceFileIn (targetFile)) | |||||
return true; | |||||
Thread::sleep (100); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// There's no temporary file to use. If your write failed, you should | |||||
// probably check, and not bother calling this method. | |||||
jassertfalse; | |||||
} | |||||
return false; | |||||
} | |||||
bool TemporaryFile::deleteTemporaryFile() const | |||||
{ | |||||
// Have a few attempts at deleting the file before giving up.. | |||||
for (int i = 5; --i >= 0;) | |||||
{ | |||||
if (temporaryFile.deleteFile()) | |||||
return true; | |||||
Thread::sleep (50); | |||||
} | |||||
return false; | |||||
} |
@@ -0,0 +1,171 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_TEMPORARYFILE_H_INCLUDED | |||||
#define JUCE_TEMPORARYFILE_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Manages a temporary file, which will be deleted when this object is deleted. | |||||
This object is intended to be used as a stack based object, using its scope | |||||
to make sure the temporary file isn't left lying around. | |||||
For example: | |||||
@code | |||||
{ | |||||
File myTargetFile ("~/myfile.txt"); | |||||
// this will choose a file called something like "~/myfile_temp239348.txt" | |||||
// which definitely doesn't exist at the time the constructor is called. | |||||
TemporaryFile temp (myTargetFile); | |||||
// create a stream to the temporary file, and write some data to it... | |||||
ScopedPointer<FileOutputStream> out (temp.getFile().createOutputStream()); | |||||
if (out != nullptr) | |||||
{ | |||||
out->write ( ...etc ) | |||||
out = nullptr; // (deletes the stream) | |||||
// ..now we've finished writing, this will rename the temp file to | |||||
// make it replace the target file we specified above. | |||||
bool succeeded = temp.overwriteTargetFileWithTemporary(); | |||||
} | |||||
// ..and even if something went wrong and our overwrite failed, | |||||
// as the TemporaryFile object goes out of scope here, it'll make sure | |||||
// that the temp file gets deleted. | |||||
} | |||||
@endcode | |||||
@see File, FileOutputStream | |||||
*/ | |||||
class JUCE_API TemporaryFile | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
enum OptionFlags | |||||
{ | |||||
useHiddenFile = 1, /**< Indicates that the temporary file should be hidden - | |||||
i.e. its name should start with a dot. */ | |||||
putNumbersInBrackets = 2 /**< Indicates that when numbers are appended to make sure | |||||
the file is unique, they should go in brackets rather | |||||
than just being appended (see File::getNonexistentSibling() )*/ | |||||
}; | |||||
//============================================================================== | |||||
/** Creates a randomly-named temporary file in the default temp directory. | |||||
@param suffix a file suffix to use for the file | |||||
@param optionFlags a combination of the values listed in the OptionFlags enum | |||||
The file will not be created until you write to it. And remember that when | |||||
this object is deleted, the file will also be deleted! | |||||
*/ | |||||
TemporaryFile (const String& suffix = String(), | |||||
int optionFlags = 0); | |||||
/** Creates a temporary file in the same directory as a specified file. | |||||
This is useful if you have a file that you want to overwrite, but don't | |||||
want to harm the original file if the write operation fails. You can | |||||
use this to create a temporary file next to the target file, then | |||||
write to the temporary file, and finally use overwriteTargetFileWithTemporary() | |||||
to replace the target file with the one you've just written. | |||||
This class won't create any files until you actually write to them. And remember | |||||
that when this object is deleted, the temporary file will also be deleted! | |||||
@param targetFile the file that you intend to overwrite - the temporary | |||||
file will be created in the same directory as this | |||||
@param optionFlags a combination of the values listed in the OptionFlags enum | |||||
*/ | |||||
TemporaryFile (const File& targetFile, | |||||
int optionFlags = 0); | |||||
/** Creates a temporary file using an explicit filename. | |||||
The other constructors are a better choice than this one, unless for some reason | |||||
you need to explicitly specify the temporary file you want to use. | |||||
@param targetFile the file that you intend to overwrite | |||||
@param temporaryFile the temporary file to be used | |||||
*/ | |||||
TemporaryFile (const File& targetFile, | |||||
const File& temporaryFile); | |||||
/** Destructor. | |||||
When this object is deleted it will make sure that its temporary file is | |||||
also deleted! If the operation fails, it'll throw an assertion in debug | |||||
mode. | |||||
*/ | |||||
~TemporaryFile(); | |||||
//============================================================================== | |||||
/** Returns the temporary file. */ | |||||
const File& getFile() const noexcept { return temporaryFile; } | |||||
/** Returns the target file that was specified in the constructor. */ | |||||
const File& getTargetFile() const noexcept { return targetFile; } | |||||
/** Tries to move the temporary file to overwrite the target file that was | |||||
specified in the constructor. | |||||
If you used the constructor that specified a target file, this will attempt | |||||
to replace that file with the temporary one. | |||||
Before calling this, make sure: | |||||
- that you've actually written to the temporary file | |||||
- that you've closed any open streams that you were using to write to it | |||||
- and that you don't have any streams open to the target file, which would | |||||
prevent it being overwritten | |||||
If the file move succeeds, this returns false, and the temporary file will | |||||
have disappeared. If it fails, the temporary file will probably still exist, | |||||
but will be deleted when this object is destroyed. | |||||
*/ | |||||
bool overwriteTargetFileWithTemporary() const; | |||||
/** Attempts to delete the temporary file, if it exists. | |||||
@returns true if the file is successfully deleted (or if it didn't exist). | |||||
*/ | |||||
bool deleteTemporaryFile() const; | |||||
private: | |||||
//============================================================================== | |||||
const File temporaryFile, targetFile; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryFile) | |||||
}; | |||||
#endif // JUCE_TEMPORARYFILE_H_INCLUDED |
@@ -18,13 +18,6 @@ | |||||
#include "juce_audio_graph.h" | #include "juce_audio_graph.h" | ||||
// #include "AppConfig.h" | |||||
// #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||||
// #include "juce_audio_processors.h" | |||||
// #include <juce_gui_extra/juce_gui_extra.h> | |||||
#include <locale> | #include <locale> | ||||
#include <cctype> | #include <cctype> | ||||
#include <cstdarg> | #include <cstdarg> | ||||
@@ -43,33 +36,13 @@ namespace juce2 | |||||
// #include "streams/juce_InputStream.cpp" | // #include "streams/juce_InputStream.cpp" | ||||
// #include "streams/juce_OutputStream.cpp" | // #include "streams/juce_OutputStream.cpp" | ||||
// static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||||
// const PluginDescription& desc) | |||||
// { | |||||
// for (int i = list.size(); --i >= 0;) | |||||
// if (list.getUnchecked(i)->isDuplicateOf (desc)) | |||||
// return true; | |||||
// | |||||
// return false; | |||||
// } | |||||
#include "midi/juce_MidiBuffer.cpp" | #include "midi/juce_MidiBuffer.cpp" | ||||
#include "midi/juce_MidiMessage.cpp" | #include "midi/juce_MidiMessage.cpp" | ||||
// #include "format/juce_AudioPluginFormat.cpp" | |||||
// #include "format/juce_AudioPluginFormatManager.cpp" | |||||
#include "processors/juce_AudioProcessor.cpp" | #include "processors/juce_AudioProcessor.cpp" | ||||
#include "processors/juce_AudioProcessorGraph.cpp" | #include "processors/juce_AudioProcessorGraph.cpp" | ||||
// #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||||
// #include "processors/juce_PluginDescription.cpp" | |||||
// #include "format_types/juce_LADSPAPluginFormat.cpp" | |||||
// #include "format_types/juce_VSTPluginFormat.cpp" | |||||
// #include "format_types/juce_VST3PluginFormat.cpp" | |||||
// #include "format_types/juce_AudioUnitPluginFormat.mm" | |||||
// #include "scanning/juce_KnownPluginList.cpp" | |||||
// #include "scanning/juce_PluginDirectoryScanner.cpp" | |||||
// #include "scanning/juce_PluginListComponent.cpp" | |||||
// #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||||
// #include "utilities/juce_AudioProcessorParameters.cpp" | |||||
#include "xml/juce_XmlElement.cpp" | |||||
#include "xml/juce_XmlDocument.cpp" | |||||
} | } |
@@ -109,11 +109,18 @@ | |||||
namespace juce2 | namespace juce2 | ||||
{ | { | ||||
class DynamicObject; | |||||
class File; | class File; | ||||
class FileInputStream; | |||||
class FileOutputStream; | |||||
class InputSource; | |||||
class InputStream; | |||||
class MidiMessage; | class MidiMessage; | ||||
class MemoryBlock; | class MemoryBlock; | ||||
class OutputStream; | class OutputStream; | ||||
class Result; | |||||
class StringRef; | class StringRef; | ||||
class XmlElement; | |||||
#include "memory/juce_Memory.h" | #include "memory/juce_Memory.h" | ||||
#include "maths/juce_MathsFunctions.h" | #include "maths/juce_MathsFunctions.h" | ||||
@@ -123,47 +130,51 @@ class StringRef; | |||||
#include "text/juce_CharPointer_UTF8.h" | #include "text/juce_CharPointer_UTF8.h" | ||||
#include "text/juce_String.h" | #include "text/juce_String.h" | ||||
#include "text/juce_StringRef.h" | |||||
#include "memory/juce_HeapBlock.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_MemoryBlock.h" | ||||
#include "memory/juce_ReferenceCountedObject.h" | #include "memory/juce_ReferenceCountedObject.h" | ||||
#include "text/juce_Identifier.h" | |||||
#include "text/juce_NewLine.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_OwnedArray.h" | ||||
#include "containers/juce_ReferenceCountedArray.h" | #include "containers/juce_ReferenceCountedArray.h" | ||||
#include "containers/juce_SortedSet.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_InputStream.h" | ||||
#include "streams/juce_OutputStream.h" | #include "streams/juce_OutputStream.h" | ||||
#include "streams/juce_MemoryOutputStream.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 "buffers/juce_AudioSampleBuffer.h" | ||||
#include "midi/juce_MidiBuffer.h" | #include "midi/juce_MidiBuffer.h" | ||||
#include "midi/juce_MidiMessage.h" | #include "midi/juce_MidiMessage.h" | ||||
class AudioProcessor; | class AudioProcessor; | ||||
#include "processors/juce_AudioPlayHead.h" | #include "processors/juce_AudioPlayHead.h" | ||||
// #include "processors/juce_AudioProcessorListener.h" | |||||
// #include "processors/juce_AudioProcessorParameter.h" | |||||
#include "processors/juce_AudioProcessor.h" | #include "processors/juce_AudioProcessor.h" | ||||
// #include "processors/juce_PluginDescription.h" | |||||
// #include "processors/juce_AudioPluginInstance.h" | |||||
#include "processors/juce_AudioProcessorGraph.h" | #include "processors/juce_AudioProcessorGraph.h" | ||||
// #include "format/juce_AudioPluginFormat.h" | |||||
// #include "format/juce_AudioPluginFormatManager.h" | |||||
// #include "scanning/juce_KnownPluginList.h" | |||||
// #include "utilities/juce_AudioProcessorValueTreeState.h" | |||||
// #include "utilities/juce_AudioProcessorParameterWithID.h" | |||||
// #include "utilities/juce_AudioParameterFloat.h" | |||||
// #include "utilities/juce_AudioParameterInt.h" | |||||
// #include "utilities/juce_AudioParameterBool.h" | |||||
// #include "utilities/juce_AudioParameterChoice.h" | |||||
#include "xml/juce_XmlElement.h" | |||||
#include "xml/juce_XmlDocument.h" | |||||
} | } | ||||
@@ -0,0 +1,24 @@ | |||||
#include "juce_audio_graph.h" | |||||
#include <iostream> | |||||
using namespace juce2; | |||||
int main() | |||||
{ | |||||
String x = "haha"; | |||||
std::cout << x << std::endl; | |||||
Atomic<float> a; | |||||
CharPointer_UTF8 c("c"); | |||||
HeapBlock<String> hs; | |||||
MemoryBlock m; | |||||
Array<CharPointer_UTF8> ar; | |||||
OwnedArray<String> ows; | |||||
AudioSampleBuffer as; | |||||
MidiBuffer mb; | |||||
MidiMessage ms; | |||||
AudioProcessorGraph g; | |||||
return 0; | |||||
} |
@@ -0,0 +1,423 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||||
#define JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A base class which provides methods for reference-counting. | |||||
To add reference-counting to a class, derive it from this class, and | |||||
use the ReferenceCountedObjectPtr class to point to it. | |||||
e.g. @code | |||||
class MyClass : public ReferenceCountedObject | |||||
{ | |||||
void foo(); | |||||
// This is a neat way of declaring a typedef for a pointer class, | |||||
// rather than typing out the full templated name each time.. | |||||
typedef ReferenceCountedObjectPtr<MyClass> Ptr; | |||||
}; | |||||
MyClass::Ptr p = new MyClass(); | |||||
MyClass::Ptr p2 = p; | |||||
p = nullptr; | |||||
p2->foo(); | |||||
@endcode | |||||
Once a new ReferenceCountedObject has been assigned to a pointer, be | |||||
careful not to delete the object manually. | |||||
This class uses an Atomic<int> value to hold the reference count, so that it | |||||
the pointers can be passed between threads safely. For a faster but non-thread-safe | |||||
version, use SingleThreadedReferenceCountedObject instead. | |||||
@see ReferenceCountedObjectPtr, ReferenceCountedArray, SingleThreadedReferenceCountedObject | |||||
*/ | |||||
class JUCE_API ReferenceCountedObject | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Increments the object's reference count. | |||||
This is done automatically by the smart pointer, but is public just | |||||
in case it's needed for nefarious purposes. | |||||
*/ | |||||
void incReferenceCount() noexcept | |||||
{ | |||||
++refCount; | |||||
} | |||||
/** Decreases the object's reference count. | |||||
If the count gets to zero, the object will be deleted. | |||||
*/ | |||||
void decReferenceCount() noexcept | |||||
{ | |||||
jassert (getReferenceCount() > 0); | |||||
if (--refCount == 0) | |||||
delete this; | |||||
} | |||||
/** Decreases the object's reference count. | |||||
If the count gets to zero, the object will not be deleted, but this method | |||||
will return true, allowing the caller to take care of deletion. | |||||
*/ | |||||
bool decReferenceCountWithoutDeleting() noexcept | |||||
{ | |||||
jassert (getReferenceCount() > 0); | |||||
return --refCount == 0; | |||||
} | |||||
/** Returns the object's current reference count. */ | |||||
int getReferenceCount() const noexcept { return refCount.get(); } | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates the reference-counted object (with an initial ref count of zero). */ | |||||
ReferenceCountedObject() {} | |||||
/** Destructor. */ | |||||
virtual ~ReferenceCountedObject() | |||||
{ | |||||
// it's dangerous to delete an object that's still referenced by something else! | |||||
jassert (getReferenceCount() == 0); | |||||
} | |||||
/** Resets the reference count to zero without deleting the object. | |||||
You should probably never need to use this! | |||||
*/ | |||||
void resetReferenceCount() noexcept | |||||
{ | |||||
refCount = 0; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
Atomic <int> refCount; | |||||
JUCE_DECLARE_NON_COPYABLE (ReferenceCountedObject) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Adds reference-counting to an object. | |||||
This is effectively a version of the ReferenceCountedObject class, but which | |||||
uses a non-atomic counter, and so is not thread-safe (but which will be more | |||||
efficient). | |||||
For more details on how to use it, see the ReferenceCountedObject class notes. | |||||
@see ReferenceCountedObject, ReferenceCountedObjectPtr, ReferenceCountedArray | |||||
*/ | |||||
class JUCE_API SingleThreadedReferenceCountedObject | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Increments the object's reference count. | |||||
This is done automatically by the smart pointer, but is public just | |||||
in case it's needed for nefarious purposes. | |||||
*/ | |||||
void incReferenceCount() noexcept | |||||
{ | |||||
++refCount; | |||||
} | |||||
/** Decreases the object's reference count. | |||||
If the count gets to zero, the object will be deleted. | |||||
*/ | |||||
void decReferenceCount() noexcept | |||||
{ | |||||
jassert (getReferenceCount() > 0); | |||||
if (--refCount == 0) | |||||
delete this; | |||||
} | |||||
/** Decreases the object's reference count. | |||||
If the count gets to zero, the object will not be deleted, but this method | |||||
will return true, allowing the caller to take care of deletion. | |||||
*/ | |||||
bool decReferenceCountWithoutDeleting() noexcept | |||||
{ | |||||
jassert (getReferenceCount() > 0); | |||||
return --refCount == 0; | |||||
} | |||||
/** Returns the object's current reference count. */ | |||||
int getReferenceCount() const noexcept { return refCount; } | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates the reference-counted object (with an initial ref count of zero). */ | |||||
SingleThreadedReferenceCountedObject() : refCount (0) {} | |||||
/** Destructor. */ | |||||
virtual ~SingleThreadedReferenceCountedObject() | |||||
{ | |||||
// it's dangerous to delete an object that's still referenced by something else! | |||||
jassert (getReferenceCount() == 0); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
int refCount; | |||||
JUCE_DECLARE_NON_COPYABLE (SingleThreadedReferenceCountedObject) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A smart-pointer class which points to a reference-counted object. | |||||
The template parameter specifies the class of the object you want to point to - the easiest | |||||
way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject | |||||
or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||||
class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||||
decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||||
should behave. | |||||
When using this class, you'll probably want to create a typedef to abbreviate the full | |||||
templated name - e.g. | |||||
@code | |||||
struct MyClass : public ReferenceCountedObject | |||||
{ | |||||
typedef ReferenceCountedObjectPtr<MyClass> Ptr; | |||||
... | |||||
@endcode | |||||
@see ReferenceCountedObject, ReferenceCountedObjectArray | |||||
*/ | |||||
template <class ReferenceCountedObjectClass> | |||||
class ReferenceCountedObjectPtr | |||||
{ | |||||
public: | |||||
/** The class being referenced by this pointer. */ | |||||
typedef ReferenceCountedObjectClass ReferencedType; | |||||
//============================================================================== | |||||
/** Creates a pointer to a null object. */ | |||||
ReferenceCountedObjectPtr() noexcept | |||||
: referencedObject (nullptr) | |||||
{ | |||||
} | |||||
/** Creates a pointer to an object. | |||||
This will increment the object's reference-count. | |||||
*/ | |||||
ReferenceCountedObjectPtr (ReferencedType* refCountedObject) noexcept | |||||
: referencedObject (refCountedObject) | |||||
{ | |||||
incIfNotNull (refCountedObject); | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_NULLPTR | |||||
/** Creates a pointer to a null object. */ | |||||
ReferenceCountedObjectPtr (decltype (nullptr)) noexcept | |||||
: referencedObject (nullptr) | |||||
{ | |||||
} | |||||
#endif | |||||
/** Copies another pointer. | |||||
This will increment the object's reference-count. | |||||
*/ | |||||
ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept | |||||
: referencedObject (other.referencedObject) | |||||
{ | |||||
incIfNotNull (referencedObject); | |||||
} | |||||
/** Copies another pointer. | |||||
This will increment the object's reference-count (if it is non-null). | |||||
*/ | |||||
template <typename Convertible> | |||||
ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr<Convertible>& other) noexcept | |||||
: referencedObject (static_cast<ReferencedType*> (other.get())) | |||||
{ | |||||
incIfNotNull (referencedObject); | |||||
} | |||||
/** Changes this pointer to point at a different object. | |||||
The reference count of the old object is decremented, and it might be | |||||
deleted if it hits zero. The new object's count is incremented. | |||||
*/ | |||||
ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) | |||||
{ | |||||
return operator= (other.referencedObject); | |||||
} | |||||
/** Changes this pointer to point at a different object. | |||||
The reference count of the old object is decremented, and it might be | |||||
deleted if it hits zero. The new object's count is incremented. | |||||
*/ | |||||
template <typename Convertible> | |||||
ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr<Convertible>& other) | |||||
{ | |||||
return operator= (static_cast<ReferencedType*> (other.get())); | |||||
} | |||||
/** Changes this pointer to point at a different object. | |||||
The reference count of the old object is decremented, and it might be | |||||
deleted if it hits zero. The new object's count is incremented. | |||||
*/ | |||||
ReferenceCountedObjectPtr& operator= (ReferencedType* const newObject) | |||||
{ | |||||
if (referencedObject != newObject) | |||||
{ | |||||
incIfNotNull (newObject); | |||||
ReferencedType* const oldObject = referencedObject; | |||||
referencedObject = newObject; | |||||
decIfNotNull (oldObject); | |||||
} | |||||
return *this; | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
/** Takes-over the object from another pointer. */ | |||||
ReferenceCountedObjectPtr (ReferenceCountedObjectPtr&& other) noexcept | |||||
: referencedObject (other.referencedObject) | |||||
{ | |||||
other.referencedObject = nullptr; | |||||
} | |||||
/** Takes-over the object from another pointer. */ | |||||
ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectPtr&& other) | |||||
{ | |||||
std::swap (referencedObject, other.referencedObject); | |||||
return *this; | |||||
} | |||||
#endif | |||||
/** Destructor. | |||||
This will decrement the object's reference-count, which will cause the | |||||
object to be deleted when the ref-count hits zero. | |||||
*/ | |||||
~ReferenceCountedObjectPtr() | |||||
{ | |||||
decIfNotNull (referencedObject); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the object that this pointer references. | |||||
The pointer returned may be null, of course. | |||||
*/ | |||||
operator ReferencedType*() const noexcept { return referencedObject; } | |||||
/** Returns the object that this pointer references. | |||||
The pointer returned may be null, of course. | |||||
*/ | |||||
ReferencedType* get() const noexcept { return referencedObject; } | |||||
/** Returns the object that this pointer references. | |||||
The pointer returned may be null, of course. | |||||
*/ | |||||
ReferencedType* getObject() const noexcept { return referencedObject; } | |||||
// the -> operator is called on the referenced object | |||||
ReferencedType* operator->() const noexcept | |||||
{ | |||||
jassert (referencedObject != nullptr); // null pointer method call! | |||||
return referencedObject; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
ReferencedType* referencedObject; | |||||
static void incIfNotNull (ReferencedType* o) noexcept | |||||
{ | |||||
if (o != nullptr) | |||||
o->incReferenceCount(); | |||||
} | |||||
static void decIfNotNull (ReferencedType* o) noexcept | |||||
{ | |||||
if (o != nullptr && o->decReferenceCountWithoutDeleting()) | |||||
delete o; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
/** Compares two ReferenceCountedObjectPtrs. */ | |||||
template <typename ReferenceCountedObjectClass> | |||||
bool operator== (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, ReferenceCountedObjectClass* const object2) noexcept | |||||
{ | |||||
return object1.get() == object2; | |||||
} | |||||
/** Compares two ReferenceCountedObjectPtrs. */ | |||||
template <typename ReferenceCountedObjectClass> | |||||
bool operator== (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||||
{ | |||||
return object1.get() == object2.get(); | |||||
} | |||||
/** Compares two ReferenceCountedObjectPtrs. */ | |||||
template <typename ReferenceCountedObjectClass> | |||||
bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||||
{ | |||||
return object1 == object2.get(); | |||||
} | |||||
/** Compares two ReferenceCountedObjectPtrs. */ | |||||
template <typename ReferenceCountedObjectClass> | |||||
bool operator!= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectClass* object2) noexcept | |||||
{ | |||||
return object1.get() != object2; | |||||
} | |||||
/** Compares two ReferenceCountedObjectPtrs. */ | |||||
template <typename ReferenceCountedObjectClass> | |||||
bool operator!= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||||
{ | |||||
return object1.get() != object2.get(); | |||||
} | |||||
/** Compares two ReferenceCountedObjectPtrs. */ | |||||
template <typename ReferenceCountedObjectClass> | |||||
bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||||
{ | |||||
return object1 != object2.get(); | |||||
} | |||||
#endif // JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED |
@@ -0,0 +1,85 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
Result::Result() noexcept {} | |||||
Result::Result (const String& message) noexcept | |||||
: errorMessage (message) | |||||
{ | |||||
} | |||||
Result::Result (const Result& other) | |||||
: errorMessage (other.errorMessage) | |||||
{ | |||||
} | |||||
Result& Result::operator= (const Result& other) | |||||
{ | |||||
errorMessage = other.errorMessage; | |||||
return *this; | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
Result::Result (Result&& other) noexcept | |||||
: errorMessage (static_cast<String&&> (other.errorMessage)) | |||||
{ | |||||
} | |||||
Result& Result::operator= (Result&& other) noexcept | |||||
{ | |||||
errorMessage = static_cast<String&&> (other.errorMessage); | |||||
return *this; | |||||
} | |||||
#endif | |||||
bool Result::operator== (const Result& other) const noexcept | |||||
{ | |||||
return errorMessage == other.errorMessage; | |||||
} | |||||
bool Result::operator!= (const Result& other) const noexcept | |||||
{ | |||||
return errorMessage != other.errorMessage; | |||||
} | |||||
Result Result::fail (const String& errorMessage) noexcept | |||||
{ | |||||
return Result (errorMessage.isEmpty() ? "Unknown Error" : errorMessage); | |||||
} | |||||
const String& Result::getErrorMessage() const noexcept | |||||
{ | |||||
return errorMessage; | |||||
} | |||||
bool Result::wasOk() const noexcept { return errorMessage.isEmpty(); } | |||||
Result::operator bool() const noexcept { return errorMessage.isEmpty(); } | |||||
bool Result::failed() const noexcept { return errorMessage.isNotEmpty(); } | |||||
bool Result::operator!() const noexcept { return errorMessage.isNotEmpty(); } |
@@ -0,0 +1,127 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_RESULT_H_INCLUDED | |||||
#define JUCE_RESULT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Represents the 'success' or 'failure' of an operation, and holds an associated | |||||
error message to describe the error when there's a failure. | |||||
E.g. | |||||
@code | |||||
Result myOperation() | |||||
{ | |||||
if (doSomeKindOfFoobar()) | |||||
return Result::ok(); | |||||
else | |||||
return Result::fail ("foobar didn't work!"); | |||||
} | |||||
const Result result (myOperation()); | |||||
if (result.wasOk()) | |||||
{ | |||||
...it's all good... | |||||
} | |||||
else | |||||
{ | |||||
warnUserAboutFailure ("The foobar operation failed! Error message was: " | |||||
+ result.getErrorMessage()); | |||||
} | |||||
@endcode | |||||
*/ | |||||
class JUCE_API Result | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates and returns a 'successful' result. */ | |||||
static Result ok() noexcept { return Result(); } | |||||
/** Creates a 'failure' result. | |||||
If you pass a blank error message in here, a default "Unknown Error" message | |||||
will be used instead. | |||||
*/ | |||||
static Result fail (const String& errorMessage) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this result indicates a success. */ | |||||
bool wasOk() const noexcept; | |||||
/** Returns true if this result indicates a failure. | |||||
You can use getErrorMessage() to retrieve the error message associated | |||||
with the failure. | |||||
*/ | |||||
bool failed() const noexcept; | |||||
/** Returns true if this result indicates a success. | |||||
This is equivalent to calling wasOk(). | |||||
*/ | |||||
operator bool() const noexcept; | |||||
/** Returns true if this result indicates a failure. | |||||
This is equivalent to calling failed(). | |||||
*/ | |||||
bool operator!() const noexcept; | |||||
/** Returns the error message that was set when this result was created. | |||||
For a successful result, this will be an empty string; | |||||
*/ | |||||
const String& getErrorMessage() const noexcept; | |||||
//============================================================================== | |||||
Result (const Result&); | |||||
Result& operator= (const Result&); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
Result (Result&&) noexcept; | |||||
Result& operator= (Result&&) noexcept; | |||||
#endif | |||||
bool operator== (const Result& other) const noexcept; | |||||
bool operator!= (const Result& other) const noexcept; | |||||
private: | |||||
String errorMessage; | |||||
// The default constructor is not for public use! | |||||
// Instead, use Result::ok() or Result::fail() | |||||
Result() noexcept; | |||||
explicit Result (const String&) noexcept; | |||||
// These casts are private to prevent people trying to use the Result object in numeric contexts | |||||
operator int() const; | |||||
operator void*() const; | |||||
}; | |||||
#endif // JUCE_RESULT_H_INCLUDED |
@@ -71,7 +71,6 @@ public: | |||||
/** The actual processor object that this node represents. */ | /** The actual processor object that this node represents. */ | ||||
AudioProcessor* getProcessor() const noexcept { return processor; } | AudioProcessor* getProcessor() const noexcept { return processor; } | ||||
#if 0 | |||||
/** A set of user-definable properties that are associated with this node. | /** 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 | 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. | is displaying the nodes on-screen. | ||||
*/ | */ | ||||
NamedValueSet properties; | NamedValueSet properties; | ||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
/** A convenient typedef for referring to a pointer to a node object. */ | /** A convenient typedef for referring to a pointer to a node object. */ | ||||
@@ -0,0 +1,58 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
FileInputSource::FileInputSource (const File& f, bool useFileTimeInHash) | |||||
: file (f), useFileTimeInHashGeneration (useFileTimeInHash) | |||||
{ | |||||
} | |||||
FileInputSource::~FileInputSource() | |||||
{ | |||||
} | |||||
InputStream* FileInputSource::createInputStream() | |||||
{ | |||||
return file.createInputStream(); | |||||
} | |||||
InputStream* FileInputSource::createInputStreamFor (const String& relatedItemPath) | |||||
{ | |||||
return file.getSiblingFile (relatedItemPath).createInputStream(); | |||||
} | |||||
int64 FileInputSource::hashCode() const | |||||
{ | |||||
int64 h = file.hashCode(); | |||||
if (useFileTimeInHashGeneration) | |||||
h ^= file.getLastModificationTime().toMilliseconds(); | |||||
return h; | |||||
} |
@@ -0,0 +1,68 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_FILEINPUTSOURCE_H_INCLUDED | |||||
#define JUCE_FILEINPUTSOURCE_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A type of InputSource that represents a normal file. | |||||
@see InputSource | |||||
*/ | |||||
class JUCE_API FileInputSource : public InputSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a FileInputSource for a file. | |||||
If the useFileTimeInHashGeneration parameter is true, then this object's | |||||
hashCode() method will incorporate the file time into its hash code; if | |||||
false, only the file name will be used for the hash. | |||||
*/ | |||||
FileInputSource (const File& file, bool useFileTimeInHashGeneration = false); | |||||
/** Destructor. */ | |||||
~FileInputSource(); | |||||
InputStream* createInputStream(); | |||||
InputStream* createInputStreamFor (const String& relatedItemPath); | |||||
int64 hashCode() const; | |||||
private: | |||||
//============================================================================== | |||||
const File file; | |||||
bool useFileTimeInHashGeneration; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputSource) | |||||
}; | |||||
#endif // JUCE_FILEINPUTSOURCE_H_INCLUDED |
@@ -0,0 +1,86 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
int64 juce_fileSetPosition (void* handle, int64 pos); | |||||
//============================================================================== | |||||
FileInputStream::FileInputStream (const File& f) | |||||
: file (f), | |||||
fileHandle (nullptr), | |||||
currentPosition (0), | |||||
status (Result::ok()) | |||||
{ | |||||
openHandle(); | |||||
} | |||||
int64 FileInputStream::getTotalLength() | |||||
{ | |||||
// You should always check that a stream opened successfully before using it! | |||||
jassert (openedOk()); | |||||
return file.getSize(); | |||||
} | |||||
int FileInputStream::read (void* buffer, int bytesToRead) | |||||
{ | |||||
// You should always check that a stream opened successfully before using it! | |||||
jassert (openedOk()); | |||||
// The buffer should never be null, and a negative size is probably a | |||||
// sign that something is broken! | |||||
jassert (buffer != nullptr && bytesToRead >= 0); | |||||
const size_t num = readInternal (buffer, (size_t) bytesToRead); | |||||
currentPosition += (int64) num; | |||||
return (int) num; | |||||
} | |||||
bool FileInputStream::isExhausted() | |||||
{ | |||||
return currentPosition >= getTotalLength(); | |||||
} | |||||
int64 FileInputStream::getPosition() | |||||
{ | |||||
return currentPosition; | |||||
} | |||||
bool FileInputStream::setPosition (int64 pos) | |||||
{ | |||||
// You should always check that a stream opened successfully before using it! | |||||
jassert (openedOk()); | |||||
if (pos != currentPosition) | |||||
currentPosition = juce_fileSetPosition (fileHandle, pos); | |||||
return currentPosition == pos; | |||||
} |
@@ -0,0 +1,98 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_FILEINPUTSTREAM_H_INCLUDED | |||||
#define JUCE_FILEINPUTSTREAM_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
An input stream that reads from a local file. | |||||
@see InputStream, FileOutputStream, File::createInputStream | |||||
*/ | |||||
class JUCE_API FileInputStream : public InputStream | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a FileInputStream to read from the given file. | |||||
After creating a FileInputStream, you should use openedOk() or failedToOpen() | |||||
to make sure that it's OK before trying to read from it! If it failed, you | |||||
can call getStatus() to get more error information. | |||||
*/ | |||||
explicit FileInputStream (const File& fileToRead); | |||||
/** Destructor. */ | |||||
~FileInputStream(); | |||||
//============================================================================== | |||||
/** Returns the file that this stream is reading from. */ | |||||
const File& getFile() const noexcept { return file; } | |||||
/** Returns the status of the file stream. | |||||
The result will be ok if the file opened successfully. If an error occurs while | |||||
opening or reading from the file, this will contain an error message. | |||||
*/ | |||||
const Result& getStatus() const noexcept { return status; } | |||||
/** Returns true if the stream couldn't be opened for some reason. | |||||
@see getResult() | |||||
*/ | |||||
bool failedToOpen() const noexcept { return status.failed(); } | |||||
/** Returns true if the stream opened without problems. | |||||
@see getResult() | |||||
*/ | |||||
bool openedOk() const noexcept { return status.wasOk(); } | |||||
//============================================================================== | |||||
int64 getTotalLength() override; | |||||
int read (void*, int) override; | |||||
bool isExhausted() override; | |||||
int64 getPosition() override; | |||||
bool setPosition (int64) override; | |||||
private: | |||||
//============================================================================== | |||||
const File file; | |||||
void* fileHandle; | |||||
int64 currentPosition; | |||||
Result status; | |||||
void openHandle(); | |||||
size_t readInternal (void*, size_t); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) | |||||
}; | |||||
#endif // JUCE_FILEINPUTSTREAM_H_INCLUDED |
@@ -0,0 +1,80 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_INPUTSOURCE_H_INCLUDED | |||||
#define JUCE_INPUTSOURCE_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A lightweight object that can create a stream to read some kind of resource. | |||||
This may be used to refer to a file, or some other kind of source, allowing a | |||||
caller to create an input stream that can read from it when required. | |||||
@see FileInputSource | |||||
*/ | |||||
class JUCE_API InputSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
InputSource() noexcept {} | |||||
/** Destructor. */ | |||||
virtual ~InputSource() {} | |||||
//============================================================================== | |||||
/** Returns a new InputStream to read this item. | |||||
@returns an inputstream that the caller will delete, or nullptr if | |||||
the filename isn't found. | |||||
*/ | |||||
virtual InputStream* createInputStream() = 0; | |||||
/** Returns a new InputStream to read an item, relative. | |||||
@param relatedItemPath the relative pathname of the resource that is required | |||||
@returns an inputstream that the caller will delete, or nullptr if | |||||
the item isn't found. | |||||
*/ | |||||
virtual InputStream* createInputStreamFor (const String& relatedItemPath) = 0; | |||||
/** Returns a hash code that uniquely represents this item. | |||||
*/ | |||||
virtual int64 hashCode() const = 0; | |||||
private: | |||||
//============================================================================== | |||||
JUCE_LEAK_DETECTOR (InputSource) | |||||
}; | |||||
#endif // JUCE_INPUTSOURCE_H_INCLUDED |
@@ -0,0 +1,79 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
Identifier::Identifier() noexcept {} | |||||
Identifier::~Identifier() noexcept {} | |||||
Identifier::Identifier (const Identifier& other) noexcept : name (other.name) {} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
Identifier::Identifier (Identifier&& other) noexcept : name (static_cast<String&&> (other.name)) {} | |||||
Identifier& Identifier::operator= (Identifier&& other) noexcept | |||||
{ | |||||
name = static_cast<String&&> (other.name); | |||||
return *this; | |||||
} | |||||
#endif | |||||
Identifier& Identifier::operator= (const Identifier& other) noexcept | |||||
{ | |||||
name = other.name; | |||||
return *this; | |||||
} | |||||
Identifier::Identifier (const String& nm) | |||||
: name (StringPool::getGlobalPool().getPooledString (nm)) | |||||
{ | |||||
// An Identifier cannot be created from an empty string! | |||||
jassert (nm.isNotEmpty()); | |||||
} | |||||
Identifier::Identifier (const char* nm) | |||||
: name (StringPool::getGlobalPool().getPooledString (nm)) | |||||
{ | |||||
// An Identifier cannot be created from an empty string! | |||||
jassert (nm != nullptr && nm[0] != 0); | |||||
} | |||||
Identifier::Identifier (String::CharPointerType start, String::CharPointerType end) | |||||
: name (StringPool::getGlobalPool().getPooledString (start, end)) | |||||
{ | |||||
// An Identifier cannot be created from an empty string! | |||||
jassert (start < end); | |||||
} | |||||
Identifier Identifier::null; | |||||
bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept | |||||
{ | |||||
return possibleIdentifier.isNotEmpty() | |||||
&& possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%"); | |||||
} |
@@ -0,0 +1,142 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_IDENTIFIER_H_INCLUDED | |||||
#define JUCE_IDENTIFIER_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Represents a string identifier, designed for accessing properties by name. | |||||
Comparing two Identifier objects is very fast (an O(1) operation), but creating | |||||
them can be slower than just using a String directly, so the optimal way to use them | |||||
is to keep some static Identifier objects for the things you use often. | |||||
@see NamedValueSet, ValueTree | |||||
*/ | |||||
class JUCE_API Identifier | |||||
{ | |||||
public: | |||||
/** Creates a null identifier. */ | |||||
Identifier() noexcept; | |||||
/** Creates an identifier with a specified name. | |||||
Because this name may need to be used in contexts such as script variables or XML | |||||
tags, it must only contain ascii letters and digits, or the underscore character. | |||||
*/ | |||||
Identifier (const char* name); | |||||
/** Creates an identifier with a specified name. | |||||
Because this name may need to be used in contexts such as script variables or XML | |||||
tags, it must only contain ascii letters and digits, or the underscore character. | |||||
*/ | |||||
Identifier (const String& name); | |||||
/** Creates an identifier with a specified name. | |||||
Because this name may need to be used in contexts such as script variables or XML | |||||
tags, it must only contain ascii letters and digits, or the underscore character. | |||||
*/ | |||||
Identifier (String::CharPointerType nameStart, String::CharPointerType nameEnd); | |||||
/** Creates a copy of another identifier. */ | |||||
Identifier (const Identifier& other) noexcept; | |||||
/** Creates a copy of another identifier. */ | |||||
Identifier& operator= (const Identifier& other) noexcept; | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
/** Creates a copy of another identifier. */ | |||||
Identifier (Identifier&& other) noexcept; | |||||
/** Creates a copy of another identifier. */ | |||||
Identifier& operator= (Identifier&& other) noexcept; | |||||
#endif | |||||
/** Destructor */ | |||||
~Identifier() noexcept; | |||||
/** Compares two identifiers. This is a very fast operation. */ | |||||
inline bool operator== (const Identifier& other) const noexcept { return name.getCharPointer() == other.name.getCharPointer(); } | |||||
/** Compares two identifiers. This is a very fast operation. */ | |||||
inline bool operator!= (const Identifier& other) const noexcept { return name.getCharPointer() != other.name.getCharPointer(); } | |||||
/** Compares the identifier with a string. */ | |||||
inline bool operator== (StringRef other) const noexcept { return name == other; } | |||||
/** Compares the identifier with a string. */ | |||||
inline bool operator!= (StringRef other) const noexcept { return name != other; } | |||||
/** Compares the identifier with a string. */ | |||||
inline bool operator< (StringRef other) const noexcept { return name < other; } | |||||
/** Compares the identifier with a string. */ | |||||
inline bool operator<= (StringRef other) const noexcept { return name <= other; } | |||||
/** Compares the identifier with a string. */ | |||||
inline bool operator> (StringRef other) const noexcept { return name > other; } | |||||
/** Compares the identifier with a string. */ | |||||
inline bool operator>= (StringRef other) const noexcept { return name >= other; } | |||||
/** Returns this identifier as a string. */ | |||||
const String& toString() const noexcept { return name; } | |||||
/** Returns this identifier's raw string pointer. */ | |||||
operator String::CharPointerType() const noexcept { return name.getCharPointer(); } | |||||
/** Returns this identifier's raw string pointer. */ | |||||
String::CharPointerType getCharPointer() const noexcept { return name.getCharPointer(); } | |||||
/** Returns this identifier as a StringRef. */ | |||||
operator StringRef() const noexcept { return name; } | |||||
/** Returns true if this Identifier is not null */ | |||||
bool isValid() const noexcept { return name.isNotEmpty(); } | |||||
/** Returns true if this Identifier is null */ | |||||
bool isNull() const noexcept { return name.isEmpty(); } | |||||
/** A null identifier. */ | |||||
static Identifier null; | |||||
/** Checks a given string for characters that might not be valid in an Identifier. | |||||
Since Identifiers are used as a script variables and XML attributes, they should only contain | |||||
alphanumeric characters, underscores, or the '-' and ':' characters. | |||||
*/ | |||||
static bool isValidIdentifier (const String& possibleIdentifier) noexcept; | |||||
private: | |||||
String name; | |||||
}; | |||||
#endif // JUCE_IDENTIFIER_H_INCLUDED |
@@ -0,0 +1,508 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
StringArray::StringArray() noexcept | |||||
{ | |||||
} | |||||
StringArray::StringArray (const StringArray& other) | |||||
: strings (other.strings) | |||||
{ | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
StringArray::StringArray (StringArray&& other) noexcept | |||||
: strings (static_cast<Array <String>&&> (other.strings)) | |||||
{ | |||||
} | |||||
#endif | |||||
StringArray::StringArray (const String& firstValue) | |||||
{ | |||||
strings.add (firstValue); | |||||
} | |||||
StringArray::StringArray (const String* initialStrings, int numberOfStrings) | |||||
{ | |||||
strings.addArray (initialStrings, numberOfStrings); | |||||
} | |||||
StringArray::StringArray (const char* const* initialStrings) | |||||
{ | |||||
strings.addNullTerminatedArray (initialStrings); | |||||
} | |||||
StringArray::StringArray (const char* const* initialStrings, int numberOfStrings) | |||||
{ | |||||
strings.addArray (initialStrings, numberOfStrings); | |||||
} | |||||
StringArray::StringArray (const wchar_t* const* initialStrings) | |||||
{ | |||||
strings.addNullTerminatedArray (initialStrings); | |||||
} | |||||
StringArray::StringArray (const wchar_t* const* initialStrings, int numberOfStrings) | |||||
{ | |||||
strings.addArray (initialStrings, numberOfStrings); | |||||
} | |||||
StringArray& StringArray::operator= (const StringArray& other) | |||||
{ | |||||
strings = other.strings; | |||||
return *this; | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
StringArray& StringArray::operator= (StringArray&& other) noexcept | |||||
{ | |||||
strings = static_cast<Array<String>&&> (other.strings); | |||||
return *this; | |||||
} | |||||
#endif | |||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
StringArray::StringArray (const std::initializer_list<const char*>& stringList) | |||||
{ | |||||
strings.addArray (stringList); | |||||
} | |||||
#endif | |||||
StringArray::~StringArray() | |||||
{ | |||||
} | |||||
bool StringArray::operator== (const StringArray& other) const noexcept | |||||
{ | |||||
return strings == other.strings; | |||||
} | |||||
bool StringArray::operator!= (const StringArray& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
void StringArray::swapWith (StringArray& other) noexcept | |||||
{ | |||||
strings.swapWith (other.strings); | |||||
} | |||||
void StringArray::clear() | |||||
{ | |||||
strings.clear(); | |||||
} | |||||
void StringArray::clearQuick() | |||||
{ | |||||
strings.clearQuick(); | |||||
} | |||||
const String& StringArray::operator[] (const int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, strings.size())) | |||||
return strings.getReference (index); | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
return String::empty; | |||||
#else | |||||
static String empty; | |||||
return empty; | |||||
#endif | |||||
} | |||||
String& StringArray::getReference (const int index) noexcept | |||||
{ | |||||
return strings.getReference (index); | |||||
} | |||||
void StringArray::add (const String& newString) | |||||
{ | |||||
strings.add (newString); | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
void StringArray::add (String&& stringToAdd) | |||||
{ | |||||
strings.add (static_cast<String&&> (stringToAdd)); | |||||
} | |||||
#endif | |||||
void StringArray::insert (const int index, const String& newString) | |||||
{ | |||||
strings.insert (index, newString); | |||||
} | |||||
bool StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) | |||||
{ | |||||
if (contains (newString, ignoreCase)) | |||||
return false; | |||||
add (newString); | |||||
return true; | |||||
} | |||||
void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) | |||||
{ | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size()) | |||||
numElementsToAdd = otherArray.size() - startIndex; | |||||
while (--numElementsToAdd >= 0) | |||||
strings.add (otherArray.strings.getReference (startIndex++)); | |||||
} | |||||
void StringArray::mergeArray (const StringArray& otherArray, const bool ignoreCase) | |||||
{ | |||||
for (int i = 0; i < otherArray.size(); ++i) | |||||
addIfNotAlreadyThere (otherArray[i], ignoreCase); | |||||
} | |||||
void StringArray::set (const int index, const String& newString) | |||||
{ | |||||
strings.set (index, newString); | |||||
} | |||||
bool StringArray::contains (StringRef stringToLookFor, const bool ignoreCase) const | |||||
{ | |||||
return indexOf (stringToLookFor, ignoreCase) >= 0; | |||||
} | |||||
int StringArray::indexOf (StringRef stringToLookFor, const bool ignoreCase, int i) const | |||||
{ | |||||
if (i < 0) | |||||
i = 0; | |||||
const int numElements = size(); | |||||
if (ignoreCase) | |||||
{ | |||||
for (; i < numElements; ++i) | |||||
if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) | |||||
return i; | |||||
} | |||||
else | |||||
{ | |||||
for (; i < numElements; ++i) | |||||
if (stringToLookFor == strings.getReference (i)) | |||||
return i; | |||||
} | |||||
return -1; | |||||
} | |||||
void StringArray::move (const int currentIndex, const int newIndex) noexcept | |||||
{ | |||||
strings.move (currentIndex, newIndex); | |||||
} | |||||
//============================================================================== | |||||
void StringArray::remove (const int index) | |||||
{ | |||||
strings.remove (index); | |||||
} | |||||
void StringArray::removeString (StringRef stringToRemove, const bool ignoreCase) | |||||
{ | |||||
if (ignoreCase) | |||||
{ | |||||
for (int i = size(); --i >= 0;) | |||||
if (strings.getReference(i).equalsIgnoreCase (stringToRemove)) | |||||
strings.remove (i); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = size(); --i >= 0;) | |||||
if (stringToRemove == strings.getReference (i)) | |||||
strings.remove (i); | |||||
} | |||||
} | |||||
void StringArray::removeRange (int startIndex, int numberToRemove) | |||||
{ | |||||
strings.removeRange (startIndex, numberToRemove); | |||||
} | |||||
//============================================================================== | |||||
void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) | |||||
{ | |||||
if (removeWhitespaceStrings) | |||||
{ | |||||
for (int i = size(); --i >= 0;) | |||||
if (! strings.getReference(i).containsNonWhitespaceChars()) | |||||
strings.remove (i); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = size(); --i >= 0;) | |||||
if (strings.getReference(i).isEmpty()) | |||||
strings.remove (i); | |||||
} | |||||
} | |||||
void StringArray::trim() | |||||
{ | |||||
for (int i = size(); --i >= 0;) | |||||
{ | |||||
String& s = strings.getReference(i); | |||||
s = s.trim(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
struct InternalStringArrayComparator_CaseSensitive | |||||
{ | |||||
static int compareElements (String& s1, String& s2) noexcept { return s1.compare (s2); } | |||||
}; | |||||
struct InternalStringArrayComparator_CaseInsensitive | |||||
{ | |||||
static int compareElements (String& s1, String& s2) noexcept { return s1.compareIgnoreCase (s2); } | |||||
}; | |||||
struct InternalStringArrayComparator_Natural | |||||
{ | |||||
static int compareElements (String& s1, String& s2) noexcept { return s1.compareNatural (s2); } | |||||
}; | |||||
void StringArray::sort (const bool ignoreCase) | |||||
{ | |||||
if (ignoreCase) | |||||
{ | |||||
InternalStringArrayComparator_CaseInsensitive comp; | |||||
strings.sort (comp); | |||||
} | |||||
else | |||||
{ | |||||
InternalStringArrayComparator_CaseSensitive comp; | |||||
strings.sort (comp); | |||||
} | |||||
} | |||||
void StringArray::sortNatural() | |||||
{ | |||||
InternalStringArrayComparator_Natural comp; | |||||
strings.sort (comp); | |||||
} | |||||
//============================================================================== | |||||
String StringArray::joinIntoString (StringRef separator, int start, int numberToJoin) const | |||||
{ | |||||
const int last = (numberToJoin < 0) ? size() | |||||
: jmin (size(), start + numberToJoin); | |||||
if (start < 0) | |||||
start = 0; | |||||
if (start >= last) | |||||
return String(); | |||||
if (start == last - 1) | |||||
return strings.getReference (start); | |||||
const size_t separatorBytes = separator.text.sizeInBytes() - sizeof (String::CharPointerType::CharType); | |||||
size_t bytesNeeded = separatorBytes * (size_t) (last - start - 1); | |||||
for (int i = start; i < last; ++i) | |||||
bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); | |||||
String result; | |||||
result.preallocateBytes (bytesNeeded); | |||||
String::CharPointerType dest (result.getCharPointer()); | |||||
while (start < last) | |||||
{ | |||||
const String& s = strings.getReference (start); | |||||
if (! s.isEmpty()) | |||||
dest.writeAll (s.getCharPointer()); | |||||
if (++start < last && separatorBytes > 0) | |||||
dest.writeAll (separator.text); | |||||
} | |||||
dest.writeNull(); | |||||
return result; | |||||
} | |||||
int StringArray::addTokens (StringRef text, const bool preserveQuotedStrings) | |||||
{ | |||||
return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); | |||||
} | |||||
int StringArray::addTokens (StringRef text, StringRef breakCharacters, StringRef quoteCharacters) | |||||
{ | |||||
int num = 0; | |||||
if (text.isNotEmpty()) | |||||
{ | |||||
for (String::CharPointerType t (text.text);;) | |||||
{ | |||||
String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t, | |||||
breakCharacters.text, | |||||
quoteCharacters.text)); | |||||
strings.add (String (t, tokenEnd)); | |||||
++num; | |||||
if (tokenEnd.isEmpty()) | |||||
break; | |||||
t = ++tokenEnd; | |||||
} | |||||
} | |||||
return num; | |||||
} | |||||
int StringArray::addLines (StringRef sourceText) | |||||
{ | |||||
int numLines = 0; | |||||
String::CharPointerType text (sourceText.text); | |||||
bool finished = text.isEmpty(); | |||||
while (! finished) | |||||
{ | |||||
for (String::CharPointerType startOfLine (text);;) | |||||
{ | |||||
const String::CharPointerType endOfLine (text); | |||||
switch (text.getAndAdvance()) | |||||
{ | |||||
case 0: finished = true; break; | |||||
case '\n': break; | |||||
case '\r': if (*text == '\n') ++text; break; | |||||
default: continue; | |||||
} | |||||
strings.add (String (startOfLine, endOfLine)); | |||||
++numLines; | |||||
break; | |||||
} | |||||
} | |||||
return numLines; | |||||
} | |||||
StringArray StringArray::fromTokens (StringRef stringToTokenise, bool preserveQuotedStrings) | |||||
{ | |||||
StringArray s; | |||||
s.addTokens (stringToTokenise, preserveQuotedStrings); | |||||
return s; | |||||
} | |||||
StringArray StringArray::fromTokens (StringRef stringToTokenise, | |||||
StringRef breakCharacters, | |||||
StringRef quoteCharacters) | |||||
{ | |||||
StringArray s; | |||||
s.addTokens (stringToTokenise, breakCharacters, quoteCharacters); | |||||
return s; | |||||
} | |||||
StringArray StringArray::fromLines (StringRef stringToBreakUp) | |||||
{ | |||||
StringArray s; | |||||
s.addLines (stringToBreakUp); | |||||
return s; | |||||
} | |||||
//============================================================================== | |||||
void StringArray::removeDuplicates (const bool ignoreCase) | |||||
{ | |||||
for (int i = 0; i < size() - 1; ++i) | |||||
{ | |||||
const String s (strings.getReference(i)); | |||||
for (int nextIndex = i + 1;;) | |||||
{ | |||||
nextIndex = indexOf (s, ignoreCase, nextIndex); | |||||
if (nextIndex < 0) | |||||
break; | |||||
strings.remove (nextIndex); | |||||
} | |||||
} | |||||
} | |||||
void StringArray::appendNumbersToDuplicates (const bool ignoreCase, | |||||
const bool appendNumberToFirstInstance, | |||||
CharPointer_UTF8 preNumberString, | |||||
CharPointer_UTF8 postNumberString) | |||||
{ | |||||
CharPointer_UTF8 defaultPre (" ("), defaultPost (")"); | |||||
if (preNumberString.getAddress() == nullptr) | |||||
preNumberString = defaultPre; | |||||
if (postNumberString.getAddress() == nullptr) | |||||
postNumberString = defaultPost; | |||||
for (int i = 0; i < size() - 1; ++i) | |||||
{ | |||||
String& s = strings.getReference(i); | |||||
int nextIndex = indexOf (s, ignoreCase, i + 1); | |||||
if (nextIndex >= 0) | |||||
{ | |||||
const String original (s); | |||||
int number = 0; | |||||
if (appendNumberToFirstInstance) | |||||
s = original + String (preNumberString) + String (++number) + String (postNumberString); | |||||
else | |||||
++number; | |||||
while (nextIndex >= 0) | |||||
{ | |||||
set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString)); | |||||
nextIndex = indexOf (original, ignoreCase, nextIndex + 1); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void StringArray::ensureStorageAllocated (int minNumElements) | |||||
{ | |||||
strings.ensureStorageAllocated (minNumElements); | |||||
} | |||||
void StringArray::minimiseStorageOverheads() | |||||
{ | |||||
strings.minimiseStorageOverheads(); | |||||
} |
@@ -0,0 +1,441 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_STRINGARRAY_H_INCLUDED | |||||
#define JUCE_STRINGARRAY_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A special array for holding a list of strings. | |||||
@see String, StringPairArray | |||||
*/ | |||||
class JUCE_API StringArray | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty string array */ | |||||
StringArray() noexcept; | |||||
/** Creates a copy of another string array */ | |||||
StringArray (const StringArray&); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
StringArray (StringArray&&) noexcept; | |||||
#endif | |||||
/** Creates an array containing a single string. */ | |||||
explicit StringArray (const String& firstValue); | |||||
/** Creates an array from a raw array of strings. | |||||
@param strings an array of strings to add | |||||
@param numberOfStrings how many items there are in the array | |||||
*/ | |||||
StringArray (const String* strings, int numberOfStrings); | |||||
/** Creates a copy of an array of string literals. | |||||
@param strings an array of strings to add. Null pointers in the array will be | |||||
treated as empty strings | |||||
@param numberOfStrings how many items there are in the array | |||||
*/ | |||||
StringArray (const char* const* strings, int numberOfStrings); | |||||
/** Creates a copy of a null-terminated array of string literals. | |||||
Each item from the array passed-in is added, until it encounters a null pointer, | |||||
at which point it stops. | |||||
*/ | |||||
explicit StringArray (const char* const* strings); | |||||
/** Creates a copy of a null-terminated array of string literals. | |||||
Each item from the array passed-in is added, until it encounters a null pointer, | |||||
at which point it stops. | |||||
*/ | |||||
explicit StringArray (const wchar_t* const* strings); | |||||
/** Creates a copy of an array of string literals. | |||||
@param strings an array of strings to add. Null pointers in the array will be | |||||
treated as empty strings | |||||
@param numberOfStrings how many items there are in the array | |||||
*/ | |||||
StringArray (const wchar_t* const* strings, int numberOfStrings); | |||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
StringArray (const std::initializer_list<const char*>& strings); | |||||
#endif | |||||
/** Destructor. */ | |||||
~StringArray(); | |||||
/** Copies the contents of another string array into this one */ | |||||
StringArray& operator= (const StringArray&); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
StringArray& operator= (StringArray&&) noexcept; | |||||
#endif | |||||
/** Swaps the contents of this and another StringArray. */ | |||||
void swapWith (StringArray&) noexcept; | |||||
//============================================================================== | |||||
/** Compares two arrays. | |||||
Comparisons are case-sensitive. | |||||
@returns true only if the other array contains exactly the same strings in the same order | |||||
*/ | |||||
bool operator== (const StringArray&) const noexcept; | |||||
/** Compares two arrays. | |||||
Comparisons are case-sensitive. | |||||
@returns false if the other array contains exactly the same strings in the same order | |||||
*/ | |||||
bool operator!= (const StringArray&) const noexcept; | |||||
//============================================================================== | |||||
/** Returns the number of strings in the array */ | |||||
inline int size() const noexcept { return strings.size(); } | |||||
/** Returns true if the array is empty, false otherwise. */ | |||||
inline bool isEmpty() const noexcept { return size() == 0; } | |||||
/** Returns one of the strings from the array. | |||||
If the index is out-of-range, an empty string is returned. | |||||
Obviously the reference returned shouldn't be stored for later use, as the | |||||
string it refers to may disappear when the array changes. | |||||
*/ | |||||
const String& operator[] (int index) const noexcept; | |||||
/** Returns a reference to one of the strings in the array. | |||||
This lets you modify a string in-place in the array, but you must be sure that | |||||
the index is in-range. | |||||
*/ | |||||
String& getReference (int index) noexcept; | |||||
/** Returns a pointer to the first String in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline String* begin() const noexcept { return strings.begin(); } | |||||
/** Returns a pointer to the String which follows the last element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline String* end() const noexcept { return strings.end(); } | |||||
/** Searches for a string in the array. | |||||
The comparison will be case-insensitive if the ignoreCase parameter is true. | |||||
@returns true if the string is found inside the array | |||||
*/ | |||||
bool contains (StringRef stringToLookFor, | |||||
bool ignoreCase = false) const; | |||||
/** Searches for a string in the array. | |||||
The comparison will be case-insensitive if the ignoreCase parameter is true. | |||||
@param stringToLookFor the string to try to find | |||||
@param ignoreCase whether the comparison should be case-insensitive | |||||
@param startIndex the first index to start searching from | |||||
@returns the index of the first occurrence of the string in this array, | |||||
or -1 if it isn't found. | |||||
*/ | |||||
int indexOf (StringRef stringToLookFor, | |||||
bool ignoreCase = false, | |||||
int startIndex = 0) const; | |||||
//============================================================================== | |||||
/** Appends a string at the end of the array. */ | |||||
void add (const String& stringToAdd); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
/** Appends a string at the end of the array. */ | |||||
void add (String&& stringToAdd); | |||||
#endif | |||||
/** Inserts a string into the array. | |||||
This will insert a string into the array at the given index, moving | |||||
up the other elements to make room for it. | |||||
If the index is less than zero or greater than the size of the array, | |||||
the new string will be added to the end of the array. | |||||
*/ | |||||
void insert (int index, const String& stringToAdd); | |||||
/** Adds a string to the array as long as it's not already in there. | |||||
The search can optionally be case-insensitive. | |||||
@return true if the string has been added, false otherwise. | |||||
*/ | |||||
bool addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false); | |||||
/** Replaces one of the strings in the array with another one. | |||||
If the index is higher than the array's size, the new string will be | |||||
added to the end of the array; if it's less than zero nothing happens. | |||||
*/ | |||||
void set (int index, const String& newString); | |||||
/** Appends some strings from another array to the end of this one. | |||||
@param other the array to add | |||||
@param startIndex the first element of the other array to add | |||||
@param numElementsToAdd the maximum number of elements to add (if this is | |||||
less than zero, they are all added) | |||||
*/ | |||||
void addArray (const StringArray& other, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1); | |||||
/** Merges the strings from another array into this one. | |||||
This will not add a string that already exists. | |||||
@param other the array to add | |||||
@param ignoreCase ignore case when merging | |||||
*/ | |||||
void mergeArray (const StringArray& other, | |||||
bool ignoreCase = false); | |||||
/** Breaks up a string into tokens and adds them to this array. | |||||
This will tokenise the given string using whitespace characters as the | |||||
token delimiters, and will add these tokens to the end of the array. | |||||
@returns the number of tokens added | |||||
@see fromTokens | |||||
*/ | |||||
int addTokens (StringRef stringToTokenise, bool preserveQuotedStrings); | |||||
/** Breaks up a string into tokens and adds them to this array. | |||||
This will tokenise the given string (using the string passed in to define the | |||||
token delimiters), and will add these tokens to the end of the array. | |||||
@param stringToTokenise the string to tokenise | |||||
@param breakCharacters a string of characters, any of which will be considered | |||||
to be a token delimiter. | |||||
@param quoteCharacters if this string isn't empty, it defines a set of characters | |||||
which are treated as quotes. Any text occurring | |||||
between quotes is not broken up into tokens. | |||||
@returns the number of tokens added | |||||
@see fromTokens | |||||
*/ | |||||
int addTokens (StringRef stringToTokenise, | |||||
StringRef breakCharacters, | |||||
StringRef quoteCharacters); | |||||
/** Breaks up a string into lines and adds them to this array. | |||||
This breaks a string down into lines separated by \\n or \\r\\n, and adds each line | |||||
to the array. Line-break characters are omitted from the strings that are added to | |||||
the array. | |||||
*/ | |||||
int addLines (StringRef stringToBreakUp); | |||||
/** Returns an array containing the tokens in a given string. | |||||
This will tokenise the given string using whitespace characters as the | |||||
token delimiters, and return the parsed tokens as an array. | |||||
@see addTokens | |||||
*/ | |||||
static StringArray fromTokens (StringRef stringToTokenise, | |||||
bool preserveQuotedStrings); | |||||
/** Returns an array containing the tokens in a given string. | |||||
This will tokenise the given string using the breakCharacters string to define | |||||
the token delimiters, and will return the parsed tokens as an array. | |||||
@param stringToTokenise the string to tokenise | |||||
@param breakCharacters a string of characters, any of which will be considered | |||||
to be a token delimiter. | |||||
@param quoteCharacters if this string isn't empty, it defines a set of characters | |||||
which are treated as quotes. Any text occurring | |||||
between quotes is not broken up into tokens. | |||||
@see addTokens | |||||
*/ | |||||
static StringArray fromTokens (StringRef stringToTokenise, | |||||
StringRef breakCharacters, | |||||
StringRef quoteCharacters); | |||||
/** Returns an array containing the lines in a given string. | |||||
This breaks a string down into lines separated by \\n or \\r\\n, and returns an | |||||
array containing these lines. Line-break characters are omitted from the strings that | |||||
are added to the array. | |||||
*/ | |||||
static StringArray fromLines (StringRef stringToBreakUp); | |||||
//============================================================================== | |||||
/** Removes all elements from the array. */ | |||||
void clear(); | |||||
/** Removes all elements from the array without freeing the array's allocated storage. | |||||
@see clear | |||||
*/ | |||||
void clearQuick(); | |||||
/** Removes a string from the array. | |||||
If the index is out-of-range, no action will be taken. | |||||
*/ | |||||
void remove (int index); | |||||
/** Finds a string in the array and removes it. | |||||
This will remove all occurrences of the given string from the array. | |||||
The comparison may be case-insensitive depending on the ignoreCase parameter. | |||||
*/ | |||||
void removeString (StringRef stringToRemove, | |||||
bool ignoreCase = false); | |||||
/** Removes a range of elements from the array. | |||||
This will remove a set of elements, starting from the given index, | |||||
and move subsequent elements down to close the gap. | |||||
If the range extends beyond the bounds of the array, it will | |||||
be safely clipped to the size of the array. | |||||
@param startIndex the index of the first element to remove | |||||
@param numberToRemove how many elements should be removed | |||||
*/ | |||||
void removeRange (int startIndex, int numberToRemove); | |||||
/** Removes any duplicated elements from the array. | |||||
If any string appears in the array more than once, only the first occurrence of | |||||
it will be retained. | |||||
@param ignoreCase whether to use a case-insensitive comparison | |||||
*/ | |||||
void removeDuplicates (bool ignoreCase); | |||||
/** Removes empty strings from the array. | |||||
@param removeWhitespaceStrings if true, strings that only contain whitespace | |||||
characters will also be removed | |||||
*/ | |||||
void removeEmptyStrings (bool removeWhitespaceStrings = true); | |||||
/** Moves one of the strings to a different position. | |||||
This will move the string to a specified index, shuffling along | |||||
any intervening elements as required. | |||||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling | |||||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||||
@param currentIndex the index of the value to be moved. If this isn't a | |||||
valid index, then nothing will be done | |||||
@param newIndex the index at which you'd like this value to end up. If this | |||||
is less than zero, the value will be moved to the end | |||||
of the array | |||||
*/ | |||||
void move (int currentIndex, int newIndex) noexcept; | |||||
/** Deletes any whitespace characters from the starts and ends of all the strings. */ | |||||
void trim(); | |||||
/** Adds numbers to the strings in the array, to make each string unique. | |||||
This will add numbers to the ends of groups of similar strings. | |||||
e.g. if there are two "moose" strings, they will become "moose (1)" and "moose (2)" | |||||
@param ignoreCaseWhenComparing whether the comparison used is case-insensitive | |||||
@param appendNumberToFirstInstance whether the first of a group of similar strings | |||||
also has a number appended to it. | |||||
@param preNumberString when adding a number, this string is added before the number. | |||||
If you pass nullptr, a default string will be used, which adds | |||||
brackets around the number. | |||||
@param postNumberString this string is appended after any numbers that are added. | |||||
If you pass nullptr, a default string will be used, which adds | |||||
brackets around the number. | |||||
*/ | |||||
void appendNumbersToDuplicates (bool ignoreCaseWhenComparing, | |||||
bool appendNumberToFirstInstance, | |||||
CharPointer_UTF8 preNumberString = CharPointer_UTF8 (nullptr), | |||||
CharPointer_UTF8 postNumberString = CharPointer_UTF8 (nullptr)); | |||||
//============================================================================== | |||||
/** Joins the strings in the array together into one string. | |||||
This will join a range of elements from the array into a string, separating | |||||
them with a given string. | |||||
e.g. joinIntoString (",") will turn an array of "a" "b" and "c" into "a,b,c". | |||||
@param separatorString the string to insert between all the strings | |||||
@param startIndex the first element to join | |||||
@param numberOfElements how many elements to join together. If this is less | |||||
than zero, all available elements will be used. | |||||
*/ | |||||
String joinIntoString (StringRef separatorString, | |||||
int startIndex = 0, | |||||
int numberOfElements = -1) const; | |||||
//============================================================================== | |||||
/** Sorts the array into alphabetical order. | |||||
@param ignoreCase if true, the comparisons used will be case-sensitive. | |||||
*/ | |||||
void sort (bool ignoreCase); | |||||
/** Sorts the array using extra language-aware rules to do a better job of comparing | |||||
words containing spaces and numbers. | |||||
@see String::compareNatural() | |||||
*/ | |||||
void sortNatural(); | |||||
//============================================================================== | |||||
/** Increases the array's internal storage to hold a minimum number of elements. | |||||
Calling this before adding a large known number of elements means that | |||||
the array won't have to keep dynamically resizing itself as the elements | |||||
are added, and it'll therefore be more efficient. | |||||
*/ | |||||
void ensureStorageAllocated (int minNumElements); | |||||
/** Reduces the amount of storage being used by the array. | |||||
Arrays typically allocate slightly more storage than they need, and after | |||||
removing elements, they may have quite a lot of unused space allocated. | |||||
This method will reduce the amount of allocated storage to a minimum. | |||||
*/ | |||||
void minimiseStorageOverheads(); | |||||
/** This is the array holding the actual strings. This is public to allow direct access | |||||
to array methods that may not already be provided by the StringArray class. | |||||
*/ | |||||
Array<String> strings; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (StringArray) | |||||
}; | |||||
#endif // JUCE_STRINGARRAY_H_INCLUDED |
@@ -0,0 +1,168 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
static const int minNumberOfStringsForGarbageCollection = 300; | |||||
static const uint32 garbageCollectionInterval = 30000; | |||||
StringPool::StringPool() noexcept : lastGarbageCollectionTime (0) {} | |||||
StringPool::~StringPool() {} | |||||
struct StartEndString | |||||
{ | |||||
StartEndString (String::CharPointerType s, String::CharPointerType e) noexcept : start (s), end (e) {} | |||||
operator String() const { return String (start, end); } | |||||
String::CharPointerType start, end; | |||||
}; | |||||
static int compareStrings (const String& s1, const String& s2) noexcept { return s1.compare (s2); } | |||||
static int compareStrings (CharPointer_UTF8 s1, const String& s2) noexcept { return s1.compare (s2.getCharPointer()); } | |||||
static int compareStrings (const StartEndString& string1, const String& string2) noexcept | |||||
{ | |||||
String::CharPointerType s1 (string1.start), s2 (string2.getCharPointer()); | |||||
for (;;) | |||||
{ | |||||
const int c1 = s1 < string1.end ? (int) s1.getAndAdvance() : 0; | |||||
const int c2 = (int) s2.getAndAdvance(); | |||||
const int diff = c1 - c2; | |||||
if (diff != 0) return diff < 0 ? -1 : 1; | |||||
if (c1 == 0) break; | |||||
} | |||||
return 0; | |||||
} | |||||
template <typename NewStringType> | |||||
static String addPooledString (Array<String>& strings, const NewStringType& newString) | |||||
{ | |||||
int start = 0; | |||||
int end = strings.size(); | |||||
while (start < end) | |||||
{ | |||||
const String& startString = strings.getReference (start); | |||||
const int startComp = compareStrings (newString, startString); | |||||
if (startComp == 0) | |||||
return startString; | |||||
const int halfway = (start + end) / 2; | |||||
if (halfway == start) | |||||
{ | |||||
if (startComp > 0) | |||||
++start; | |||||
break; | |||||
} | |||||
const String& halfwayString = strings.getReference (halfway); | |||||
const int halfwayComp = compareStrings (newString, halfwayString); | |||||
if (halfwayComp == 0) | |||||
return halfwayString; | |||||
if (halfwayComp > 0) | |||||
start = halfway; | |||||
else | |||||
end = halfway; | |||||
} | |||||
strings.insert (start, newString); | |||||
return strings.getReference (start); | |||||
} | |||||
String StringPool::getPooledString (const char* const newString) | |||||
{ | |||||
if (newString == nullptr || *newString == 0) | |||||
return String(); | |||||
const ScopedLock sl (lock); | |||||
garbageCollectIfNeeded(); | |||||
return addPooledString (strings, CharPointer_UTF8 (newString)); | |||||
} | |||||
String StringPool::getPooledString (String::CharPointerType start, String::CharPointerType end) | |||||
{ | |||||
if (start.isEmpty() || start == end) | |||||
return String(); | |||||
const ScopedLock sl (lock); | |||||
garbageCollectIfNeeded(); | |||||
return addPooledString (strings, StartEndString (start, end)); | |||||
} | |||||
String StringPool::getPooledString (StringRef newString) | |||||
{ | |||||
if (newString.isEmpty()) | |||||
return String(); | |||||
const ScopedLock sl (lock); | |||||
garbageCollectIfNeeded(); | |||||
return addPooledString (strings, newString.text); | |||||
} | |||||
String StringPool::getPooledString (const String& newString) | |||||
{ | |||||
if (newString.isEmpty()) | |||||
return String(); | |||||
const ScopedLock sl (lock); | |||||
garbageCollectIfNeeded(); | |||||
return addPooledString (strings, newString); | |||||
} | |||||
void StringPool::garbageCollectIfNeeded() | |||||
{ | |||||
if (strings.size() > minNumberOfStringsForGarbageCollection | |||||
&& Time::getApproximateMillisecondCounter() > lastGarbageCollectionTime + garbageCollectionInterval) | |||||
garbageCollect(); | |||||
} | |||||
void StringPool::garbageCollect() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = strings.size(); --i >= 0;) | |||||
if (strings.getReference(i).getReferenceCount() == 1) | |||||
strings.remove (i); | |||||
lastGarbageCollectionTime = Time::getApproximateMillisecondCounter(); | |||||
} | |||||
StringPool& StringPool::getGlobalPool() noexcept | |||||
{ | |||||
static StringPool pool; | |||||
return pool; | |||||
} |
@@ -0,0 +1,98 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_STRINGPOOL_H_INCLUDED | |||||
#define JUCE_STRINGPOOL_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A StringPool holds a set of shared strings, which reduces storage overheads and improves | |||||
comparison speed when dealing with many duplicate strings. | |||||
When you add a string to a pool using getPooledString, it'll return a character | |||||
array containing the same string. This array is owned by the pool, and the same array | |||||
is returned every time a matching string is asked for. This means that it's trivial to | |||||
compare two pooled strings for equality, as you can simply compare their pointers. It | |||||
also cuts down on storage if you're using many copies of the same string. | |||||
*/ | |||||
class JUCE_API StringPool | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty pool. */ | |||||
StringPool() noexcept; | |||||
/** Destructor */ | |||||
~StringPool(); | |||||
//============================================================================== | |||||
/** Returns a pointer to a shared copy of the string that is passed in. | |||||
The pool will always return the same String object when asked for a string that matches it. | |||||
*/ | |||||
String getPooledString (const String& original); | |||||
/** Returns a pointer to a copy of the string that is passed in. | |||||
The pool will always return the same String object when asked for a string that matches it. | |||||
*/ | |||||
String getPooledString (const char* original); | |||||
/** Returns a pointer to a shared copy of the string that is passed in. | |||||
The pool will always return the same String object when asked for a string that matches it. | |||||
*/ | |||||
String getPooledString (StringRef original); | |||||
/** Returns a pointer to a copy of the string that is passed in. | |||||
The pool will always return the same String object when asked for a string that matches it. | |||||
*/ | |||||
String getPooledString (String::CharPointerType start, String::CharPointerType end); | |||||
//============================================================================== | |||||
/** Scans the pool, and removes any strings that are unreferenced. | |||||
You don't generally need to call this - it'll be called automatically when the pool grows | |||||
large enough to warrant it. | |||||
*/ | |||||
void garbageCollect(); | |||||
/** Returns a shared global pool which is used for things like Identifiers, XML parsing. */ | |||||
static StringPool& getGlobalPool() noexcept; | |||||
private: | |||||
Array<String> strings; | |||||
CarlaRecursiveMutex lock; | |||||
uint32 lastGarbageCollectionTime; | |||||
void garbageCollectIfNeeded(); | |||||
JUCE_DECLARE_NON_COPYABLE (StringPool) | |||||
}; | |||||
#endif // JUCE_STRINGPOOL_H_INCLUDED |
@@ -0,0 +1,895 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
XmlDocument::XmlDocument (const String& documentText) | |||||
: originalText (documentText), | |||||
input (nullptr), | |||||
outOfData (false), | |||||
errorOccurred (false), | |||||
needToLoadDTD (false), | |||||
ignoreEmptyTextElements (true) | |||||
{ | |||||
} | |||||
XmlDocument::XmlDocument (const File& file) | |||||
: input (nullptr), | |||||
outOfData (false), | |||||
errorOccurred (false), | |||||
needToLoadDTD (false), | |||||
ignoreEmptyTextElements (true), | |||||
inputSource (new FileInputSource (file)) | |||||
{ | |||||
} | |||||
XmlDocument::~XmlDocument() | |||||
{ | |||||
} | |||||
XmlElement* XmlDocument::parse (const File& file) | |||||
{ | |||||
XmlDocument doc (file); | |||||
return doc.getDocumentElement(); | |||||
} | |||||
XmlElement* XmlDocument::parse (const String& xmlData) | |||||
{ | |||||
XmlDocument doc (xmlData); | |||||
return doc.getDocumentElement(); | |||||
} | |||||
void XmlDocument::setInputSource (InputSource* const newSource) noexcept | |||||
{ | |||||
inputSource = newSource; | |||||
} | |||||
void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept | |||||
{ | |||||
ignoreEmptyTextElements = shouldBeIgnored; | |||||
} | |||||
namespace XmlIdentifierChars | |||||
{ | |||||
static bool isIdentifierCharSlow (const juce_wchar c) noexcept | |||||
{ | |||||
return CharacterFunctions::isLetterOrDigit (c) | |||||
|| c == '_' || c == '-' || c == ':' || c == '.'; | |||||
} | |||||
static bool isIdentifierChar (const juce_wchar c) noexcept | |||||
{ | |||||
static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 }; | |||||
return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0) | |||||
: isIdentifierCharSlow (c); | |||||
} | |||||
/*static void generateIdentifierCharConstants() | |||||
{ | |||||
uint32 n[8] = { 0 }; | |||||
for (int i = 0; i < 256; ++i) | |||||
if (isIdentifierCharSlow (i)) | |||||
n[i >> 5] |= (1 << (i & 31)); | |||||
String s; | |||||
for (int i = 0; i < 8; ++i) | |||||
s << "0x" << String::toHexString ((int) n[i]) << ", "; | |||||
DBG (s); | |||||
}*/ | |||||
static String::CharPointerType findEndOfToken (String::CharPointerType p) | |||||
{ | |||||
while (isIdentifierChar (*p)) | |||||
++p; | |||||
return p; | |||||
} | |||||
} | |||||
XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) | |||||
{ | |||||
if (originalText.isEmpty() && inputSource != nullptr) | |||||
{ | |||||
ScopedPointer<InputStream> in (inputSource->createInputStream()); | |||||
if (in != nullptr) | |||||
{ | |||||
MemoryOutputStream data; | |||||
data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); | |||||
#if JUCE_STRING_UTF_TYPE == 8 | |||||
if (data.getDataSize() > 2) | |||||
{ | |||||
data.writeByte (0); | |||||
const char* text = static_cast<const char*> (data.getData()); | |||||
if (CharPointer_UTF16::isByteOrderMarkBigEndian (text) | |||||
|| CharPointer_UTF16::isByteOrderMarkLittleEndian (text)) | |||||
{ | |||||
originalText = data.toString(); | |||||
} | |||||
else | |||||
{ | |||||
if (CharPointer_UTF8::isByteOrderMark (text)) | |||||
text += 3; | |||||
// parse the input buffer directly to avoid copying it all to a string.. | |||||
return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement); | |||||
} | |||||
} | |||||
#else | |||||
originalText = data.toString(); | |||||
#endif | |||||
} | |||||
} | |||||
return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement); | |||||
} | |||||
const String& XmlDocument::getLastParseError() const noexcept | |||||
{ | |||||
return lastError; | |||||
} | |||||
void XmlDocument::setLastError (const String& desc, const bool carryOn) | |||||
{ | |||||
lastError = desc; | |||||
errorOccurred = ! carryOn; | |||||
} | |||||
String XmlDocument::getFileContents (const String& filename) const | |||||
{ | |||||
if (inputSource != nullptr) | |||||
{ | |||||
const ScopedPointer<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted())); | |||||
if (in != nullptr) | |||||
return in->readEntireStreamAsString(); | |||||
} | |||||
return String(); | |||||
} | |||||
juce_wchar XmlDocument::readNextChar() noexcept | |||||
{ | |||||
const juce_wchar c = input.getAndAdvance(); | |||||
if (c == 0) | |||||
{ | |||||
outOfData = true; | |||||
--input; | |||||
} | |||||
return c; | |||||
} | |||||
XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToParse, | |||||
const bool onlyReadOuterDocumentElement) | |||||
{ | |||||
input = textToParse; | |||||
errorOccurred = false; | |||||
outOfData = false; | |||||
needToLoadDTD = true; | |||||
if (textToParse.isEmpty()) | |||||
{ | |||||
lastError = "not enough input"; | |||||
} | |||||
else if (! parseHeader()) | |||||
{ | |||||
lastError = "malformed header"; | |||||
} | |||||
else if (! parseDTD()) | |||||
{ | |||||
lastError = "malformed DTD"; | |||||
} | |||||
else | |||||
{ | |||||
lastError.clear(); | |||||
ScopedPointer<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement)); | |||||
if (! errorOccurred) | |||||
return result.release(); | |||||
} | |||||
return nullptr; | |||||
} | |||||
bool XmlDocument::parseHeader() | |||||
{ | |||||
skipNextWhiteSpace(); | |||||
if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<?xml"), 5) == 0) | |||||
{ | |||||
const String::CharPointerType headerEnd (CharacterFunctions::find (input, CharPointer_UTF8 ("?>"))); | |||||
if (headerEnd.isEmpty()) | |||||
return false; | |||||
#if JUCE_DEBUG | |||||
const String encoding (String (input, headerEnd) | |||||
.fromFirstOccurrenceOf ("encoding", false, true) | |||||
.fromFirstOccurrenceOf ("=", false, false) | |||||
.fromFirstOccurrenceOf ("\"", false, false) | |||||
.upToFirstOccurrenceOf ("\"", false, false).trim()); | |||||
/* If you load an XML document with a non-UTF encoding type, it may have been | |||||
loaded wrongly.. Since all the files are read via the normal juce file streams, | |||||
they're treated as UTF-8, so by the time it gets to the parser, the encoding will | |||||
have been lost. Best plan is to stick to utf-8 or if you have specific files to | |||||
read, use your own code to convert them to a unicode String, and pass that to the | |||||
XML parser. | |||||
*/ | |||||
jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-")); | |||||
#endif | |||||
input = headerEnd + 2; | |||||
skipNextWhiteSpace(); | |||||
} | |||||
return true; | |||||
} | |||||
bool XmlDocument::parseDTD() | |||||
{ | |||||
if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<!DOCTYPE"), 9) == 0) | |||||
{ | |||||
input += 9; | |||||
const String::CharPointerType dtdStart (input); | |||||
for (int n = 1; n > 0;) | |||||
{ | |||||
const juce_wchar c = readNextChar(); | |||||
if (outOfData) | |||||
return false; | |||||
if (c == '<') | |||||
++n; | |||||
else if (c == '>') | |||||
--n; | |||||
} | |||||
dtdText = String (dtdStart, input - 1).trim(); | |||||
} | |||||
return true; | |||||
} | |||||
void XmlDocument::skipNextWhiteSpace() | |||||
{ | |||||
for (;;) | |||||
{ | |||||
input = input.findEndOfWhitespace(); | |||||
if (input.isEmpty()) | |||||
{ | |||||
outOfData = true; | |||||
break; | |||||
} | |||||
if (*input == '<') | |||||
{ | |||||
if (input[1] == '!' | |||||
&& input[2] == '-' | |||||
&& input[3] == '-') | |||||
{ | |||||
input += 4; | |||||
const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); | |||||
if (closeComment < 0) | |||||
{ | |||||
outOfData = true; | |||||
break; | |||||
} | |||||
input += closeComment + 3; | |||||
continue; | |||||
} | |||||
if (input[1] == '?') | |||||
{ | |||||
input += 2; | |||||
const int closeBracket = input.indexOf (CharPointer_UTF8 ("?>")); | |||||
if (closeBracket < 0) | |||||
{ | |||||
outOfData = true; | |||||
break; | |||||
} | |||||
input += closeBracket + 2; | |||||
continue; | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
void XmlDocument::readQuotedString (String& result) | |||||
{ | |||||
const juce_wchar quote = readNextChar(); | |||||
while (! outOfData) | |||||
{ | |||||
const juce_wchar c = readNextChar(); | |||||
if (c == quote) | |||||
break; | |||||
--input; | |||||
if (c == '&') | |||||
{ | |||||
readEntity (result); | |||||
} | |||||
else | |||||
{ | |||||
const String::CharPointerType start (input); | |||||
for (;;) | |||||
{ | |||||
const juce_wchar character = *input; | |||||
if (character == quote) | |||||
{ | |||||
result.appendCharPointer (start, input); | |||||
++input; | |||||
return; | |||||
} | |||||
else if (character == '&') | |||||
{ | |||||
result.appendCharPointer (start, input); | |||||
break; | |||||
} | |||||
else if (character == 0) | |||||
{ | |||||
setLastError ("unmatched quotes", false); | |||||
outOfData = true; | |||||
break; | |||||
} | |||||
++input; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||||
{ | |||||
XmlElement* node = nullptr; | |||||
skipNextWhiteSpace(); | |||||
if (outOfData) | |||||
return nullptr; | |||||
if (*input == '<') | |||||
{ | |||||
++input; | |||||
String::CharPointerType endOfToken (XmlIdentifierChars::findEndOfToken (input)); | |||||
if (endOfToken == input) | |||||
{ | |||||
// no tag name - but allow for a gap after the '<' before giving an error | |||||
skipNextWhiteSpace(); | |||||
endOfToken = XmlIdentifierChars::findEndOfToken (input); | |||||
if (endOfToken == input) | |||||
{ | |||||
setLastError ("tag name missing", false); | |||||
return node; | |||||
} | |||||
} | |||||
node = new XmlElement (input, endOfToken); | |||||
input = endOfToken; | |||||
LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes); | |||||
// look for attributes | |||||
for (;;) | |||||
{ | |||||
skipNextWhiteSpace(); | |||||
const juce_wchar c = *input; | |||||
// empty tag.. | |||||
if (c == '/' && input[1] == '>') | |||||
{ | |||||
input += 2; | |||||
break; | |||||
} | |||||
// parse the guts of the element.. | |||||
if (c == '>') | |||||
{ | |||||
++input; | |||||
if (alsoParseSubElements) | |||||
readChildElements (*node); | |||||
break; | |||||
} | |||||
// get an attribute.. | |||||
if (XmlIdentifierChars::isIdentifierChar (c)) | |||||
{ | |||||
String::CharPointerType attNameEnd (XmlIdentifierChars::findEndOfToken (input)); | |||||
if (attNameEnd != input) | |||||
{ | |||||
const String::CharPointerType attNameStart (input); | |||||
input = attNameEnd; | |||||
skipNextWhiteSpace(); | |||||
if (readNextChar() == '=') | |||||
{ | |||||
skipNextWhiteSpace(); | |||||
const juce_wchar nextChar = *input; | |||||
if (nextChar == '"' || nextChar == '\'') | |||||
{ | |||||
XmlElement::XmlAttributeNode* const newAtt | |||||
= new XmlElement::XmlAttributeNode (attNameStart, attNameEnd); | |||||
readQuotedString (newAtt->value); | |||||
attributeAppender.append (newAtt); | |||||
continue; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
setLastError ("expected '=' after attribute '" | |||||
+ String (attNameStart, attNameEnd) + "'", false); | |||||
return node; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (! outOfData) | |||||
setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
return node; | |||||
} | |||||
void XmlDocument::readChildElements (XmlElement& parent) | |||||
{ | |||||
LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement); | |||||
for (;;) | |||||
{ | |||||
const String::CharPointerType preWhitespaceInput (input); | |||||
skipNextWhiteSpace(); | |||||
if (outOfData) | |||||
{ | |||||
setLastError ("unmatched tags", false); | |||||
break; | |||||
} | |||||
if (*input == '<') | |||||
{ | |||||
const juce_wchar c1 = input[1]; | |||||
if (c1 == '/') | |||||
{ | |||||
// our close tag.. | |||||
const int closeTag = input.indexOf ((juce_wchar) '>'); | |||||
if (closeTag >= 0) | |||||
input += closeTag + 1; | |||||
break; | |||||
} | |||||
if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_UTF8 ("[CDATA["), 7) == 0) | |||||
{ | |||||
input += 9; | |||||
const String::CharPointerType inputStart (input); | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c0 = *input; | |||||
if (c0 == 0) | |||||
{ | |||||
setLastError ("unterminated CDATA section", false); | |||||
outOfData = true; | |||||
break; | |||||
} | |||||
else if (c0 == ']' | |||||
&& input[1] == ']' | |||||
&& input[2] == '>') | |||||
{ | |||||
childAppender.append (XmlElement::createTextElement (String (inputStart, input))); | |||||
input += 3; | |||||
break; | |||||
} | |||||
++input; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// this is some other element, so parse and add it.. | |||||
if (XmlElement* const n = readNextElement (true)) | |||||
childAppender.append (n); | |||||
else | |||||
break; | |||||
} | |||||
} | |||||
else // must be a character block | |||||
{ | |||||
input = preWhitespaceInput; // roll back to include the leading whitespace | |||||
MemoryOutputStream textElementContent; | |||||
bool contentShouldBeUsed = ! ignoreEmptyTextElements; | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = *input; | |||||
if (c == '<') | |||||
{ | |||||
if (input[1] == '!' && input[2] == '-' && input[3] == '-') | |||||
{ | |||||
input += 4; | |||||
const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); | |||||
if (closeComment < 0) | |||||
{ | |||||
setLastError ("unterminated comment", false); | |||||
outOfData = true; | |||||
return; | |||||
} | |||||
input += closeComment + 3; | |||||
continue; | |||||
} | |||||
break; | |||||
} | |||||
if (c == 0) | |||||
{ | |||||
setLastError ("unmatched tags", false); | |||||
outOfData = true; | |||||
return; | |||||
} | |||||
if (c == '&') | |||||
{ | |||||
String entity; | |||||
readEntity (entity); | |||||
if (entity.startsWithChar ('<') && entity [1] != 0) | |||||
{ | |||||
const String::CharPointerType oldInput (input); | |||||
const bool oldOutOfData = outOfData; | |||||
input = entity.getCharPointer(); | |||||
outOfData = false; | |||||
while (XmlElement* n = readNextElement (true)) | |||||
childAppender.append (n); | |||||
input = oldInput; | |||||
outOfData = oldOutOfData; | |||||
} | |||||
else | |||||
{ | |||||
textElementContent << entity; | |||||
contentShouldBeUsed = contentShouldBeUsed || entity.containsNonWhitespaceChars(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (;; ++input) | |||||
{ | |||||
juce_wchar nextChar = *input; | |||||
if (nextChar == '\r') | |||||
{ | |||||
nextChar = '\n'; | |||||
if (input[1] == '\n') | |||||
continue; | |||||
} | |||||
if (nextChar == '<' || nextChar == '&') | |||||
break; | |||||
if (nextChar == 0) | |||||
{ | |||||
setLastError ("unmatched tags", false); | |||||
outOfData = true; | |||||
return; | |||||
} | |||||
textElementContent.appendUTF8Char (nextChar); | |||||
contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar); | |||||
} | |||||
} | |||||
} | |||||
if (contentShouldBeUsed) | |||||
childAppender.append (XmlElement::createTextElement (textElementContent.toUTF8())); | |||||
} | |||||
} | |||||
} | |||||
void XmlDocument::readEntity (String& result) | |||||
{ | |||||
// skip over the ampersand | |||||
++input; | |||||
if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("amp;"), 4) == 0) | |||||
{ | |||||
input += 4; | |||||
result += '&'; | |||||
} | |||||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("quot;"), 5) == 0) | |||||
{ | |||||
input += 5; | |||||
result += '"'; | |||||
} | |||||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("apos;"), 5) == 0) | |||||
{ | |||||
input += 5; | |||||
result += '\''; | |||||
} | |||||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("lt;"), 3) == 0) | |||||
{ | |||||
input += 3; | |||||
result += '<'; | |||||
} | |||||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("gt;"), 3) == 0) | |||||
{ | |||||
input += 3; | |||||
result += '>'; | |||||
} | |||||
else if (*input == '#') | |||||
{ | |||||
int charCode = 0; | |||||
++input; | |||||
if (*input == 'x' || *input == 'X') | |||||
{ | |||||
++input; | |||||
int numChars = 0; | |||||
while (input[0] != ';') | |||||
{ | |||||
const int hexValue = CharacterFunctions::getHexDigitValue (input[0]); | |||||
if (hexValue < 0 || ++numChars > 8) | |||||
{ | |||||
setLastError ("illegal escape sequence", true); | |||||
break; | |||||
} | |||||
charCode = (charCode << 4) | hexValue; | |||||
++input; | |||||
} | |||||
++input; | |||||
} | |||||
else if (input[0] >= '0' && input[0] <= '9') | |||||
{ | |||||
int numChars = 0; | |||||
while (input[0] != ';') | |||||
{ | |||||
if (++numChars > 12) | |||||
{ | |||||
setLastError ("illegal escape sequence", true); | |||||
break; | |||||
} | |||||
charCode = charCode * 10 + ((int) input[0] - '0'); | |||||
++input; | |||||
} | |||||
++input; | |||||
} | |||||
else | |||||
{ | |||||
setLastError ("illegal escape sequence", true); | |||||
result += '&'; | |||||
return; | |||||
} | |||||
#if 0 // FIXME | |||||
result << (juce_wchar) charCode; | |||||
#endif | |||||
} | |||||
else | |||||
{ | |||||
const String::CharPointerType entityNameStart (input); | |||||
const int closingSemiColon = input.indexOf ((juce_wchar) ';'); | |||||
if (closingSemiColon < 0) | |||||
{ | |||||
outOfData = true; | |||||
result += '&'; | |||||
} | |||||
else | |||||
{ | |||||
input += closingSemiColon + 1; | |||||
result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon)); | |||||
} | |||||
} | |||||
} | |||||
String XmlDocument::expandEntity (const String& ent) | |||||
{ | |||||
if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&'); | |||||
if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"'); | |||||
if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\''); | |||||
if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<'); | |||||
if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>'); | |||||
if (ent[0] == '#') | |||||
{ | |||||
const juce_wchar char1 = ent[1]; | |||||
if (char1 == 'x' || char1 == 'X') | |||||
return String::charToString (static_cast<juce_wchar> (ent.substring (2).getHexValue32())); | |||||
if (char1 >= '0' && char1 <= '9') | |||||
return String::charToString (static_cast<juce_wchar> (ent.substring (1).getIntValue())); | |||||
setLastError ("illegal escape sequence", false); | |||||
return String::charToString ('&'); | |||||
} | |||||
return expandExternalEntity (ent); | |||||
} | |||||
String XmlDocument::expandExternalEntity (const String& entity) | |||||
{ | |||||
if (needToLoadDTD) | |||||
{ | |||||
if (dtdText.isNotEmpty()) | |||||
{ | |||||
dtdText = dtdText.trimCharactersAtEnd (">"); | |||||
tokenisedDTD.addTokens (dtdText, true); | |||||
if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") | |||||
&& tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) | |||||
{ | |||||
const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); | |||||
tokenisedDTD.clear(); | |||||
tokenisedDTD.addTokens (getFileContents (fn), true); | |||||
} | |||||
else | |||||
{ | |||||
tokenisedDTD.clear(); | |||||
const int openBracket = dtdText.indexOfChar ('['); | |||||
if (openBracket > 0) | |||||
{ | |||||
const int closeBracket = dtdText.lastIndexOfChar (']'); | |||||
if (closeBracket > openBracket) | |||||
tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, | |||||
closeBracket), true); | |||||
} | |||||
} | |||||
for (int i = tokenisedDTD.size(); --i >= 0;) | |||||
{ | |||||
if (tokenisedDTD[i].startsWithChar ('%') | |||||
&& tokenisedDTD[i].endsWithChar (';')) | |||||
{ | |||||
const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); | |||||
StringArray newToks; | |||||
newToks.addTokens (parsed, true); | |||||
tokenisedDTD.remove (i); | |||||
for (int j = newToks.size(); --j >= 0;) | |||||
tokenisedDTD.insert (i, newToks[j]); | |||||
} | |||||
} | |||||
} | |||||
needToLoadDTD = false; | |||||
} | |||||
for (int i = 0; i < tokenisedDTD.size(); ++i) | |||||
{ | |||||
if (tokenisedDTD[i] == entity) | |||||
{ | |||||
if (tokenisedDTD[i - 1].equalsIgnoreCase ("<!entity")) | |||||
{ | |||||
String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">").trim().unquoted()); | |||||
// check for sub-entities.. | |||||
int ampersand = ent.indexOfChar ('&'); | |||||
while (ampersand >= 0) | |||||
{ | |||||
const int semiColon = ent.indexOf (i + 1, ";"); | |||||
if (semiColon < 0) | |||||
{ | |||||
setLastError ("entity without terminating semi-colon", false); | |||||
break; | |||||
} | |||||
const String resolved (expandEntity (ent.substring (i + 1, semiColon))); | |||||
ent = ent.substring (0, ampersand) | |||||
+ resolved | |||||
+ ent.substring (semiColon + 1); | |||||
ampersand = ent.indexOfChar (semiColon + 1, '&'); | |||||
} | |||||
return ent; | |||||
} | |||||
} | |||||
} | |||||
setLastError ("unknown entity", true); | |||||
return entity; | |||||
} | |||||
String XmlDocument::getParameterEntity (const String& entity) | |||||
{ | |||||
for (int i = 0; i < tokenisedDTD.size(); ++i) | |||||
{ | |||||
if (tokenisedDTD[i] == entity | |||||
&& tokenisedDTD [i - 1] == "%" | |||||
&& tokenisedDTD [i - 2].equalsIgnoreCase ("<!entity")) | |||||
{ | |||||
const String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">")); | |||||
if (ent.equalsIgnoreCase ("system")) | |||||
return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); | |||||
return ent.trim().unquoted(); | |||||
} | |||||
} | |||||
return entity; | |||||
} |
@@ -0,0 +1,183 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_XMLDOCUMENT_H_INCLUDED | |||||
#define JUCE_XMLDOCUMENT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Parses a text-based XML document and creates an XmlElement object from it. | |||||
The parser will parse DTDs to load external entities but won't | |||||
check the document for validity against the DTD. | |||||
e.g. | |||||
@code | |||||
XmlDocument myDocument (File ("myfile.xml")); | |||||
ScopedPointer<XmlElement> mainElement (myDocument.getDocumentElement()); | |||||
if (mainElement == nullptr) | |||||
{ | |||||
String error = myDocument.getLastParseError(); | |||||
} | |||||
else | |||||
{ | |||||
..use the element | |||||
} | |||||
@endcode | |||||
Or you can use the static helper methods for quick parsing.. | |||||
@code | |||||
ScopedPointer<XmlElement> xml (XmlDocument::parse (myXmlFile)); | |||||
if (xml != nullptr && xml->hasTagName ("foobar")) | |||||
{ | |||||
...etc | |||||
@endcode | |||||
@see XmlElement | |||||
*/ | |||||
class JUCE_API XmlDocument | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an XmlDocument from the xml text. | |||||
The text doesn't actually get parsed until the getDocumentElement() method is called. | |||||
*/ | |||||
XmlDocument (const String& documentText); | |||||
/** Creates an XmlDocument from a file. | |||||
The text doesn't actually get parsed until the getDocumentElement() method is called. | |||||
*/ | |||||
XmlDocument (const File& file); | |||||
/** Destructor. */ | |||||
~XmlDocument(); | |||||
//============================================================================== | |||||
/** Creates an XmlElement object to represent the main document node. | |||||
This method will do the actual parsing of the text, and if there's a | |||||
parse error, it may returns nullptr (and you can find out the error using | |||||
the getLastParseError() method). | |||||
See also the parse() methods, which provide a shorthand way to quickly | |||||
parse a file or string. | |||||
@param onlyReadOuterDocumentElement if true, the parser will only read the | |||||
first section of the file, and will only | |||||
return the outer document element - this | |||||
allows quick checking of large files to | |||||
see if they contain the correct type of | |||||
tag, without having to parse the entire file | |||||
@returns a new XmlElement which the caller will need to delete, or null if | |||||
there was an error. | |||||
@see getLastParseError | |||||
*/ | |||||
XmlElement* getDocumentElement (bool onlyReadOuterDocumentElement = false); | |||||
/** Returns the parsing error that occurred the last time getDocumentElement was called. | |||||
@returns the error, or an empty string if there was no error. | |||||
*/ | |||||
const String& getLastParseError() const noexcept; | |||||
/** Sets an input source object to use for parsing documents that reference external entities. | |||||
If the document has been created from a file, this probably won't be needed, but | |||||
if you're parsing some text and there might be a DTD that references external | |||||
files, you may need to create a custom input source that can retrieve the | |||||
other files it needs. | |||||
The object that is passed-in will be deleted automatically when no longer needed. | |||||
@see InputSource | |||||
*/ | |||||
void setInputSource (InputSource* newSource) noexcept; | |||||
/** Sets a flag to change the treatment of empty text elements. | |||||
If this is true (the default state), then any text elements that contain only | |||||
whitespace characters will be ingored during parsing. If you need to catch | |||||
whitespace-only text, then you should set this to false before calling the | |||||
getDocumentElement() method. | |||||
*/ | |||||
void setEmptyTextElementsIgnored (bool shouldBeIgnored) noexcept; | |||||
//============================================================================== | |||||
/** A handy static method that parses a file. | |||||
This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. | |||||
@returns a new XmlElement which the caller will need to delete, or null if there was an error. | |||||
*/ | |||||
static XmlElement* parse (const File& file); | |||||
/** A handy static method that parses some XML data. | |||||
This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. | |||||
@returns a new XmlElement which the caller will need to delete, or null if there was an error. | |||||
*/ | |||||
static XmlElement* parse (const String& xmlData); | |||||
//============================================================================== | |||||
private: | |||||
String originalText; | |||||
String::CharPointerType input; | |||||
bool outOfData, errorOccurred; | |||||
String lastError, dtdText; | |||||
StringArray tokenisedDTD; | |||||
bool needToLoadDTD, ignoreEmptyTextElements; | |||||
ScopedPointer<InputSource> inputSource; | |||||
XmlElement* parseDocumentElement (String::CharPointerType, bool outer); | |||||
void setLastError (const String&, bool carryOn); | |||||
bool parseHeader(); | |||||
bool parseDTD(); | |||||
void skipNextWhiteSpace(); | |||||
juce_wchar readNextChar() noexcept; | |||||
XmlElement* readNextElement (bool alsoParseSubElements); | |||||
void readChildElements (XmlElement&); | |||||
void readQuotedString (String&); | |||||
void readEntity (String&); | |||||
String getFileContents (const String&) const; | |||||
String expandEntity (const String&); | |||||
String expandExternalEntity (const String&); | |||||
String getParameterEntity (const String&); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) | |||||
}; | |||||
#endif // JUCE_XMLDOCUMENT_H_INCLUDED |
@@ -0,0 +1,942 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace | |||||
{ | |||||
inline bool isValidXmlNameStartCharacter (const juce_wchar character) noexcept | |||||
{ | |||||
return character == ':' | |||||
|| character == '_' | |||||
|| (character >= 'a' && character <= 'z') | |||||
|| (character >= 'A' && character <= 'Z') | |||||
|| (character >= 0xc0 && character <= 0xd6) | |||||
|| (character >= 0xd8 && character <= 0xf6) | |||||
|| (character >= 0xf8 && character <= 0x2ff) | |||||
|| (character >= 0x370 && character <= 0x37d) | |||||
|| (character >= 0x37f && character <= 0x1fff) | |||||
|| (character >= 0x200c && character <= 0x200d) | |||||
|| (character >= 0x2070 && character <= 0x218f) | |||||
|| (character >= 0x2c00 && character <= 0x2fef) | |||||
|| (character >= 0x3001 && character <= 0xd7ff) | |||||
|| (character >= 0xf900 && character <= 0xfdcf) | |||||
|| (character >= 0xfdf0 && character <= 0xfffd) | |||||
|| (character >= 0x10000 && character <= 0xeffff); | |||||
} | |||||
inline bool isValidXmlNameBodyCharacter (const juce_wchar character) noexcept | |||||
{ | |||||
return isValidXmlNameStartCharacter (character) | |||||
|| character == '-' | |||||
|| character == '.' | |||||
|| character == 0xb7 | |||||
|| (character >= '0' && character <= '9') | |||||
|| (character >= 0x300 && character <= 0x036f) | |||||
|| (character >= 0x203f && character <= 0x2040); | |||||
} | |||||
} | |||||
XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept | |||||
: name (other.name), | |||||
value (other.value) | |||||
{ | |||||
} | |||||
XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept | |||||
: name (n), value (v) | |||||
{ | |||||
jassert (isValidXmlName (name)); | |||||
} | |||||
XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd) | |||||
: name (nameStart, nameEnd) | |||||
{ | |||||
jassert (isValidXmlName (name)); | |||||
} | |||||
//============================================================================== | |||||
XmlElement::XmlElement (const String& tag) | |||||
: tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||||
{ | |||||
jassert (isValidXmlName (tagName)); | |||||
} | |||||
XmlElement::XmlElement (const char* tag) | |||||
: tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||||
{ | |||||
jassert (isValidXmlName (tagName)); | |||||
} | |||||
XmlElement::XmlElement (StringRef tag) | |||||
: tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||||
{ | |||||
jassert (isValidXmlName (tagName)); | |||||
} | |||||
XmlElement::XmlElement (const Identifier& tag) | |||||
: tagName (tag.toString()) | |||||
{ | |||||
jassert (isValidXmlName (tagName)); | |||||
} | |||||
XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd) | |||||
: tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd)) | |||||
{ | |||||
jassert (isValidXmlName (tagName)); | |||||
} | |||||
XmlElement::XmlElement (int /*dummy*/) noexcept | |||||
{ | |||||
} | |||||
XmlElement::XmlElement (const XmlElement& other) | |||||
: tagName (other.tagName) | |||||
{ | |||||
copyChildrenAndAttributesFrom (other); | |||||
} | |||||
XmlElement& XmlElement::operator= (const XmlElement& other) | |||||
{ | |||||
if (this != &other) | |||||
{ | |||||
removeAllAttributes(); | |||||
deleteAllChildElements(); | |||||
tagName = other.tagName; | |||||
copyChildrenAndAttributesFrom (other); | |||||
} | |||||
return *this; | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
XmlElement::XmlElement (XmlElement&& other) noexcept | |||||
: nextListItem (static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem)), | |||||
firstChildElement (static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement)), | |||||
attributes (static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes)), | |||||
tagName (static_cast<String&&> (other.tagName)) | |||||
{ | |||||
} | |||||
XmlElement& XmlElement::operator= (XmlElement&& other) noexcept | |||||
{ | |||||
jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||||
removeAllAttributes(); | |||||
deleteAllChildElements(); | |||||
nextListItem = static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem); | |||||
firstChildElement = static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement); | |||||
attributes = static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes); | |||||
tagName = static_cast<String&&> (other.tagName); | |||||
return *this; | |||||
} | |||||
#endif | |||||
void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) | |||||
{ | |||||
jassert (firstChildElement.get() == nullptr); | |||||
firstChildElement.addCopyOfList (other.firstChildElement); | |||||
jassert (attributes.get() == nullptr); | |||||
attributes.addCopyOfList (other.attributes); | |||||
} | |||||
XmlElement::~XmlElement() noexcept | |||||
{ | |||||
firstChildElement.deleteAll(); | |||||
attributes.deleteAll(); | |||||
} | |||||
//============================================================================== | |||||
namespace XmlOutputFunctions | |||||
{ | |||||
#if 0 // (These functions are just used to generate the lookup table used below) | |||||
bool isLegalXmlCharSlow (const juce_wchar character) noexcept | |||||
{ | |||||
if ((character >= 'a' && character <= 'z') | |||||
|| (character >= 'A' && character <= 'Z') | |||||
|| (character >= '0' && character <= '9')) | |||||
return true; | |||||
const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; | |||||
do | |||||
{ | |||||
if (((juce_wchar) (uint8) *t) == character) | |||||
return true; | |||||
} | |||||
while (*++t != 0); | |||||
return false; | |||||
} | |||||
void generateLegalCharLookupTable() | |||||
{ | |||||
uint8 n[32] = { 0 }; | |||||
for (int i = 0; i < 256; ++i) | |||||
if (isLegalXmlCharSlow (i)) | |||||
n[i >> 3] |= (1 << (i & 7)); | |||||
String s; | |||||
for (int i = 0; i < 32; ++i) | |||||
s << (int) n[i] << ", "; | |||||
DBG (s); | |||||
} | |||||
#endif | |||||
static bool isLegalXmlChar (const uint32 c) noexcept | |||||
{ | |||||
static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, | |||||
255, 255, 191, 254, 255, 255, 127 }; | |||||
return c < sizeof (legalChars) * 8 | |||||
&& (legalChars [c >> 3] & (1 << (c & 7))) != 0; | |||||
} | |||||
static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) | |||||
{ | |||||
String::CharPointerType t (text.getCharPointer()); | |||||
for (;;) | |||||
{ | |||||
const uint32 character = (uint32) t.getAndAdvance(); | |||||
if (character == 0) | |||||
break; | |||||
if (isLegalXmlChar (character)) | |||||
{ | |||||
outputStream << (char) character; | |||||
} | |||||
else | |||||
{ | |||||
switch (character) | |||||
{ | |||||
case '&': outputStream << "&"; break; | |||||
case '"': outputStream << """; break; | |||||
case '>': outputStream << ">"; break; | |||||
case '<': outputStream << "<"; break; | |||||
case '\n': | |||||
case '\r': | |||||
if (! changeNewLines) | |||||
{ | |||||
outputStream << (char) character; | |||||
break; | |||||
} | |||||
// Note: deliberate fall-through here! | |||||
default: | |||||
outputStream << "&#" << ((int) character) << ';'; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
static void writeSpaces (OutputStream& out, const size_t numSpaces) | |||||
{ | |||||
out.writeRepeatedByte (' ', numSpaces); | |||||
} | |||||
} | |||||
void XmlElement::writeElementAsText (OutputStream& outputStream, | |||||
const int indentationLevel, | |||||
const int lineWrapLength) const | |||||
{ | |||||
using namespace XmlOutputFunctions; | |||||
if (indentationLevel >= 0) | |||||
writeSpaces (outputStream, (size_t) indentationLevel); | |||||
if (! isTextElement()) | |||||
{ | |||||
outputStream.writeByte ('<'); | |||||
outputStream << tagName; | |||||
{ | |||||
const size_t attIndent = (size_t) (indentationLevel + tagName.length() + 1); | |||||
int lineLen = 0; | |||||
for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||||
{ | |||||
if (lineLen > lineWrapLength && indentationLevel >= 0) | |||||
{ | |||||
outputStream << newLine; | |||||
writeSpaces (outputStream, attIndent); | |||||
lineLen = 0; | |||||
} | |||||
const int64 startPos = outputStream.getPosition(); | |||||
outputStream.writeByte (' '); | |||||
outputStream << att->name; | |||||
outputStream.write ("=\"", 2); | |||||
escapeIllegalXmlChars (outputStream, att->value, true); | |||||
outputStream.writeByte ('"'); | |||||
lineLen += (int) (outputStream.getPosition() - startPos); | |||||
} | |||||
} | |||||
if (firstChildElement != nullptr) | |||||
{ | |||||
outputStream.writeByte ('>'); | |||||
bool lastWasTextNode = false; | |||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||||
{ | |||||
if (child->isTextElement()) | |||||
{ | |||||
escapeIllegalXmlChars (outputStream, child->getText(), false); | |||||
lastWasTextNode = true; | |||||
} | |||||
else | |||||
{ | |||||
if (indentationLevel >= 0 && ! lastWasTextNode) | |||||
outputStream << newLine; | |||||
child->writeElementAsText (outputStream, | |||||
lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength); | |||||
lastWasTextNode = false; | |||||
} | |||||
} | |||||
if (indentationLevel >= 0 && ! lastWasTextNode) | |||||
{ | |||||
outputStream << newLine; | |||||
writeSpaces (outputStream, (size_t) indentationLevel); | |||||
} | |||||
outputStream.write ("</", 2); | |||||
outputStream << tagName; | |||||
outputStream.writeByte ('>'); | |||||
} | |||||
else | |||||
{ | |||||
outputStream.write ("/>", 2); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
escapeIllegalXmlChars (outputStream, getText(), false); | |||||
} | |||||
} | |||||
String XmlElement::createDocument (StringRef dtdToUse, | |||||
const bool allOnOneLine, | |||||
const bool includeXmlHeader, | |||||
StringRef encodingType, | |||||
const int lineWrapLength) const | |||||
{ | |||||
MemoryOutputStream mem (2048); | |||||
writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); | |||||
return mem.toUTF8(); | |||||
} | |||||
void XmlElement::writeToStream (OutputStream& output, | |||||
StringRef dtdToUse, | |||||
const bool allOnOneLine, | |||||
const bool includeXmlHeader, | |||||
StringRef encodingType, | |||||
const int lineWrapLength) const | |||||
{ | |||||
using namespace XmlOutputFunctions; | |||||
if (includeXmlHeader) | |||||
{ | |||||
output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>"; | |||||
if (allOnOneLine) | |||||
output.writeByte (' '); | |||||
else | |||||
output << newLine << newLine; | |||||
} | |||||
if (dtdToUse.isNotEmpty()) | |||||
{ | |||||
output << dtdToUse; | |||||
if (allOnOneLine) | |||||
output.writeByte (' '); | |||||
else | |||||
output << newLine; | |||||
} | |||||
writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength); | |||||
if (! allOnOneLine) | |||||
output << newLine; | |||||
} | |||||
#if 0 | |||||
bool XmlElement::writeToFile (const File& file, | |||||
StringRef dtdToUse, | |||||
StringRef encodingType, | |||||
const int lineWrapLength) const | |||||
{ | |||||
TemporaryFile tempFile (file); | |||||
{ | |||||
FileOutputStream out (tempFile.getFile()); | |||||
if (! out.openedOk()) | |||||
return false; | |||||
writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength); | |||||
out.flush(); // (called explicitly to force an fsync on posix) | |||||
if (out.getStatus().failed()) | |||||
return false; | |||||
} | |||||
return tempFile.overwriteTargetFileWithTemporary(); | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
bool XmlElement::hasTagName (StringRef possibleTagName) const noexcept | |||||
{ | |||||
const bool matches = tagName.equalsIgnoreCase (possibleTagName); | |||||
// XML tags should be case-sensitive, so although this method allows a | |||||
// case-insensitive match to pass, you should try to avoid this. | |||||
jassert ((! matches) || tagName == possibleTagName); | |||||
return matches; | |||||
} | |||||
String XmlElement::getNamespace() const | |||||
{ | |||||
return tagName.upToFirstOccurrenceOf (":", false, false); | |||||
} | |||||
String XmlElement::getTagNameWithoutNamespace() const | |||||
{ | |||||
return tagName.fromLastOccurrenceOf (":", false, false); | |||||
} | |||||
bool XmlElement::hasTagNameIgnoringNamespace (StringRef possibleTagName) const | |||||
{ | |||||
return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName; | |||||
} | |||||
XmlElement* XmlElement::getNextElementWithTagName (StringRef requiredTagName) const | |||||
{ | |||||
XmlElement* e = nextListItem; | |||||
while (e != nullptr && ! e->hasTagName (requiredTagName)) | |||||
e = e->nextListItem; | |||||
return e; | |||||
} | |||||
//============================================================================== | |||||
int XmlElement::getNumAttributes() const noexcept | |||||
{ | |||||
return attributes.size(); | |||||
} | |||||
static const String& getEmptyStringRef() noexcept | |||||
{ | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
return String::empty; | |||||
#else | |||||
static String empty; | |||||
return empty; | |||||
#endif | |||||
} | |||||
const String& XmlElement::getAttributeName (const int index) const noexcept | |||||
{ | |||||
if (const XmlAttributeNode* const att = attributes [index]) | |||||
return att->name.toString(); | |||||
return getEmptyStringRef(); | |||||
} | |||||
const String& XmlElement::getAttributeValue (const int index) const noexcept | |||||
{ | |||||
if (const XmlAttributeNode* const att = attributes [index]) | |||||
return att->value; | |||||
return getEmptyStringRef(); | |||||
} | |||||
XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept | |||||
{ | |||||
for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||||
if (att->name == attributeName) | |||||
return att; | |||||
return nullptr; | |||||
} | |||||
bool XmlElement::hasAttribute (StringRef attributeName) const noexcept | |||||
{ | |||||
return getAttribute (attributeName) != nullptr; | |||||
} | |||||
//============================================================================== | |||||
const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept | |||||
{ | |||||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||||
return att->value; | |||||
return getEmptyStringRef(); | |||||
} | |||||
String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const | |||||
{ | |||||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||||
return att->value; | |||||
return defaultReturnValue; | |||||
} | |||||
int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const | |||||
{ | |||||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||||
return att->value.getIntValue(); | |||||
return defaultReturnValue; | |||||
} | |||||
double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const | |||||
{ | |||||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||||
return att->value.getDoubleValue(); | |||||
return defaultReturnValue; | |||||
} | |||||
bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const | |||||
{ | |||||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||||
{ | |||||
const juce_wchar firstChar = *(att->value.getCharPointer().findEndOfWhitespace()); | |||||
return firstChar == '1' | |||||
|| firstChar == 't' | |||||
|| firstChar == 'y' | |||||
|| firstChar == 'T' | |||||
|| firstChar == 'Y'; | |||||
} | |||||
return defaultReturnValue; | |||||
} | |||||
bool XmlElement::compareAttribute (StringRef attributeName, | |||||
StringRef stringToCompareAgainst, | |||||
const bool ignoreCase) const noexcept | |||||
{ | |||||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||||
return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst) | |||||
: att->value == stringToCompareAgainst; | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
void XmlElement::setAttribute (const Identifier& attributeName, const String& value) | |||||
{ | |||||
if (attributes == nullptr) | |||||
{ | |||||
attributes = new XmlAttributeNode (attributeName, value); | |||||
} | |||||
else | |||||
{ | |||||
for (XmlAttributeNode* att = attributes; ; att = att->nextListItem) | |||||
{ | |||||
if (att->name == attributeName) | |||||
{ | |||||
att->value = value; | |||||
break; | |||||
} | |||||
if (att->nextListItem == nullptr) | |||||
{ | |||||
att->nextListItem = new XmlAttributeNode (attributeName, value); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void XmlElement::setAttribute (const Identifier& attributeName, const int number) | |||||
{ | |||||
setAttribute (attributeName, String (number)); | |||||
} | |||||
void XmlElement::setAttribute (const Identifier& attributeName, const double number) | |||||
{ | |||||
setAttribute (attributeName, String (number, 20)); | |||||
} | |||||
void XmlElement::removeAttribute (const Identifier& attributeName) noexcept | |||||
{ | |||||
for (LinkedListPointer<XmlAttributeNode>* att = &attributes; | |||||
att->get() != nullptr; | |||||
att = &(att->get()->nextListItem)) | |||||
{ | |||||
if (att->get()->name == attributeName) | |||||
{ | |||||
delete att->removeNext(); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
void XmlElement::removeAllAttributes() noexcept | |||||
{ | |||||
attributes.deleteAll(); | |||||
} | |||||
//============================================================================== | |||||
int XmlElement::getNumChildElements() const noexcept | |||||
{ | |||||
return firstChildElement.size(); | |||||
} | |||||
XmlElement* XmlElement::getChildElement (const int index) const noexcept | |||||
{ | |||||
return firstChildElement [index].get(); | |||||
} | |||||
XmlElement* XmlElement::getChildByName (StringRef childName) const noexcept | |||||
{ | |||||
jassert (! childName.isEmpty()); | |||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||||
if (child->hasTagName (childName)) | |||||
return child; | |||||
return nullptr; | |||||
} | |||||
XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept | |||||
{ | |||||
jassert (! attributeName.isEmpty()); | |||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||||
if (child->compareAttribute (attributeName, attributeValue)) | |||||
return child; | |||||
return nullptr; | |||||
} | |||||
void XmlElement::addChildElement (XmlElement* const newNode) noexcept | |||||
{ | |||||
if (newNode != nullptr) | |||||
{ | |||||
// The element being added must not be a child of another node! | |||||
jassert (newNode->nextListItem == nullptr); | |||||
firstChildElement.append (newNode); | |||||
} | |||||
} | |||||
void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept | |||||
{ | |||||
if (newNode != nullptr) | |||||
{ | |||||
// The element being added must not be a child of another node! | |||||
jassert (newNode->nextListItem == nullptr); | |||||
firstChildElement.insertAtIndex (indexToInsertAt, newNode); | |||||
} | |||||
} | |||||
void XmlElement::prependChildElement (XmlElement* newNode) noexcept | |||||
{ | |||||
if (newNode != nullptr) | |||||
{ | |||||
// The element being added must not be a child of another node! | |||||
jassert (newNode->nextListItem == nullptr); | |||||
firstChildElement.insertNext (newNode); | |||||
} | |||||
} | |||||
XmlElement* XmlElement::createNewChildElement (StringRef childTagName) | |||||
{ | |||||
XmlElement* const newElement = new XmlElement (childTagName); | |||||
addChildElement (newElement); | |||||
return newElement; | |||||
} | |||||
bool XmlElement::replaceChildElement (XmlElement* const currentChildElement, | |||||
XmlElement* const newNode) noexcept | |||||
{ | |||||
if (newNode != nullptr) | |||||
{ | |||||
if (LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement)) | |||||
{ | |||||
if (currentChildElement != newNode) | |||||
delete p->replaceNext (newNode); | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void XmlElement::removeChildElement (XmlElement* const childToRemove, | |||||
const bool shouldDeleteTheChild) noexcept | |||||
{ | |||||
if (childToRemove != nullptr) | |||||
{ | |||||
firstChildElement.remove (childToRemove); | |||||
if (shouldDeleteTheChild) | |||||
delete childToRemove; | |||||
} | |||||
} | |||||
bool XmlElement::isEquivalentTo (const XmlElement* const other, | |||||
const bool ignoreOrderOfAttributes) const noexcept | |||||
{ | |||||
if (this != other) | |||||
{ | |||||
if (other == nullptr || tagName != other->tagName) | |||||
return false; | |||||
if (ignoreOrderOfAttributes) | |||||
{ | |||||
int totalAtts = 0; | |||||
for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||||
{ | |||||
if (! other->compareAttribute (att->name, att->value)) | |||||
return false; | |||||
++totalAtts; | |||||
} | |||||
if (totalAtts != other->getNumAttributes()) | |||||
return false; | |||||
} | |||||
else | |||||
{ | |||||
const XmlAttributeNode* thisAtt = attributes; | |||||
const XmlAttributeNode* otherAtt = other->attributes; | |||||
for (;;) | |||||
{ | |||||
if (thisAtt == nullptr || otherAtt == nullptr) | |||||
{ | |||||
if (thisAtt == otherAtt) // both nullptr, so it's a match | |||||
break; | |||||
return false; | |||||
} | |||||
if (thisAtt->name != otherAtt->name | |||||
|| thisAtt->value != otherAtt->value) | |||||
{ | |||||
return false; | |||||
} | |||||
thisAtt = thisAtt->nextListItem; | |||||
otherAtt = otherAtt->nextListItem; | |||||
} | |||||
} | |||||
const XmlElement* thisChild = firstChildElement; | |||||
const XmlElement* otherChild = other->firstChildElement; | |||||
for (;;) | |||||
{ | |||||
if (thisChild == nullptr || otherChild == nullptr) | |||||
{ | |||||
if (thisChild == otherChild) // both 0, so it's a match | |||||
break; | |||||
return false; | |||||
} | |||||
if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes)) | |||||
return false; | |||||
thisChild = thisChild->nextListItem; | |||||
otherChild = otherChild->nextListItem; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
void XmlElement::deleteAllChildElements() noexcept | |||||
{ | |||||
firstChildElement.deleteAll(); | |||||
} | |||||
void XmlElement::deleteAllChildElementsWithTagName (StringRef name) noexcept | |||||
{ | |||||
for (XmlElement* child = firstChildElement; child != nullptr;) | |||||
{ | |||||
XmlElement* const nextChild = child->nextListItem; | |||||
if (child->hasTagName (name)) | |||||
removeChildElement (child, true); | |||||
child = nextChild; | |||||
} | |||||
} | |||||
bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept | |||||
{ | |||||
return firstChildElement.contains (possibleChild); | |||||
} | |||||
XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept | |||||
{ | |||||
if (this == elementToLookFor || elementToLookFor == nullptr) | |||||
return nullptr; | |||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||||
{ | |||||
if (elementToLookFor == child) | |||||
return this; | |||||
if (XmlElement* const found = child->findParentElementOf (elementToLookFor)) | |||||
return found; | |||||
} | |||||
return nullptr; | |||||
} | |||||
void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept | |||||
{ | |||||
firstChildElement.copyToArray (elems); | |||||
} | |||||
void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept | |||||
{ | |||||
XmlElement* e = firstChildElement = elems[0]; | |||||
for (int i = 1; i < num; ++i) | |||||
{ | |||||
e->nextListItem = elems[i]; | |||||
e = e->nextListItem; | |||||
} | |||||
e->nextListItem = nullptr; | |||||
} | |||||
//============================================================================== | |||||
bool XmlElement::isTextElement() const noexcept | |||||
{ | |||||
return tagName.isEmpty(); | |||||
} | |||||
static const String juce_xmltextContentAttributeName ("text"); | |||||
const String& XmlElement::getText() const noexcept | |||||
{ | |||||
jassert (isTextElement()); // you're trying to get the text from an element that | |||||
// isn't actually a text element.. If this contains text sub-nodes, you | |||||
// probably want to use getAllSubText instead. | |||||
return getStringAttribute (juce_xmltextContentAttributeName); | |||||
} | |||||
void XmlElement::setText (const String& newText) | |||||
{ | |||||
if (isTextElement()) | |||||
setAttribute (juce_xmltextContentAttributeName, newText); | |||||
else | |||||
jassertfalse; // you can only change the text in a text element, not a normal one. | |||||
} | |||||
String XmlElement::getAllSubText() const | |||||
{ | |||||
if (isTextElement()) | |||||
return getText(); | |||||
if (getNumChildElements() == 1) | |||||
return firstChildElement.get()->getAllSubText(); | |||||
MemoryOutputStream mem (1024); | |||||
for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||||
mem << child->getAllSubText(); | |||||
return mem.toUTF8(); | |||||
} | |||||
String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const | |||||
{ | |||||
if (const XmlElement* const child = getChildByName (childTagName)) | |||||
return child->getAllSubText(); | |||||
return defaultReturnValue; | |||||
} | |||||
XmlElement* XmlElement::createTextElement (const String& text) | |||||
{ | |||||
XmlElement* const e = new XmlElement ((int) 0); | |||||
e->setAttribute (juce_xmltextContentAttributeName, text); | |||||
return e; | |||||
} | |||||
bool XmlElement::isValidXmlName (StringRef text) noexcept | |||||
{ | |||||
if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance())) | |||||
return false; | |||||
for (;;) | |||||
{ | |||||
if (text.isEmpty()) | |||||
return true; | |||||
if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance())) | |||||
return false; | |||||
} | |||||
} | |||||
void XmlElement::addTextElement (const String& text) | |||||
{ | |||||
addChildElement (createTextElement (text)); | |||||
} | |||||
void XmlElement::deleteAllTextElements() noexcept | |||||
{ | |||||
for (XmlElement* child = firstChildElement; child != nullptr;) | |||||
{ | |||||
XmlElement* const next = child->nextListItem; | |||||
if (child->isTextElement()) | |||||
removeChildElement (child, true); | |||||
child = next; | |||||
} | |||||
} |
@@ -0,0 +1,775 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_XMLELEMENT_H_INCLUDED | |||||
#define JUCE_XMLELEMENT_H_INCLUDED | |||||
//============================================================================== | |||||
/** A handy macro to make it easy to iterate all the child elements in an XmlElement. | |||||
The parentXmlElement should be a reference to the parent XML, and the childElementVariableName | |||||
will be the name of a pointer to each child element. | |||||
E.g. @code | |||||
XmlElement* myParentXml = createSomeKindOfXmlDocument(); | |||||
forEachXmlChildElement (*myParentXml, child) | |||||
{ | |||||
if (child->hasTagName ("FOO")) | |||||
doSomethingWithXmlElement (child); | |||||
} | |||||
@endcode | |||||
@see forEachXmlChildElementWithTagName | |||||
*/ | |||||
#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \ | |||||
\ | |||||
for (juce::XmlElement* childElementVariableName = (parentXmlElement).getFirstChildElement(); \ | |||||
childElementVariableName != nullptr; \ | |||||
childElementVariableName = childElementVariableName->getNextElement()) | |||||
/** A macro that makes it easy to iterate all the child elements of an XmlElement | |||||
which have a specified tag. | |||||
This does the same job as the forEachXmlChildElement macro, but only for those | |||||
elements that have a particular tag name. | |||||
The parentXmlElement should be a reference to the parent XML, and the childElementVariableName | |||||
will be the name of a pointer to each child element. The requiredTagName is the | |||||
tag name to match. | |||||
E.g. @code | |||||
XmlElement* myParentXml = createSomeKindOfXmlDocument(); | |||||
forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG") | |||||
{ | |||||
// the child object is now guaranteed to be a <MYTAG> element.. | |||||
doSomethingWithMYTAGElement (child); | |||||
} | |||||
@endcode | |||||
@see forEachXmlChildElement | |||||
*/ | |||||
#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \ | |||||
\ | |||||
for (juce::XmlElement* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \ | |||||
childElementVariableName != nullptr; \ | |||||
childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName)) | |||||
//============================================================================== | |||||
/** Used to build a tree of elements representing an XML document. | |||||
An XML document can be parsed into a tree of XmlElements, each of which | |||||
represents an XML tag structure, and which may itself contain other | |||||
nested elements. | |||||
An XmlElement can also be converted back into a text document, and has | |||||
lots of useful methods for manipulating its attributes and sub-elements, | |||||
so XmlElements can actually be used as a handy general-purpose data | |||||
structure. | |||||
Here's an example of parsing some elements: @code | |||||
// check we're looking at the right kind of document.. | |||||
if (myElement->hasTagName ("ANIMALS")) | |||||
{ | |||||
// now we'll iterate its sub-elements looking for 'giraffe' elements.. | |||||
forEachXmlChildElement (*myElement, e) | |||||
{ | |||||
if (e->hasTagName ("GIRAFFE")) | |||||
{ | |||||
// found a giraffe, so use some of its attributes.. | |||||
String giraffeName = e->getStringAttribute ("name"); | |||||
int giraffeAge = e->getIntAttribute ("age"); | |||||
bool isFriendly = e->getBoolAttribute ("friendly"); | |||||
} | |||||
} | |||||
} | |||||
@endcode | |||||
And here's an example of how to create an XML document from scratch: @code | |||||
// create an outer node called "ANIMALS" | |||||
XmlElement animalsList ("ANIMALS"); | |||||
for (int i = 0; i < numAnimals; ++i) | |||||
{ | |||||
// create an inner element.. | |||||
XmlElement* giraffe = new XmlElement ("GIRAFFE"); | |||||
giraffe->setAttribute ("name", "nigel"); | |||||
giraffe->setAttribute ("age", 10); | |||||
giraffe->setAttribute ("friendly", true); | |||||
// ..and add our new element to the parent node | |||||
animalsList.addChildElement (giraffe); | |||||
} | |||||
// now we can turn the whole thing into a text document.. | |||||
String myXmlDoc = animalsList.createDocument (String()); | |||||
@endcode | |||||
@see XmlDocument | |||||
*/ | |||||
class JUCE_API XmlElement | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an XmlElement with this tag name. */ | |||||
explicit XmlElement (const String& tagName); | |||||
/** Creates an XmlElement with this tag name. */ | |||||
explicit XmlElement (const char* tagName); | |||||
/** Creates an XmlElement with this tag name. */ | |||||
explicit XmlElement (const Identifier& tagName); | |||||
/** Creates an XmlElement with this tag name. */ | |||||
explicit XmlElement (StringRef tagName); | |||||
/** Creates an XmlElement with this tag name. */ | |||||
XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd); | |||||
/** Creates a (deep) copy of another element. */ | |||||
XmlElement (const XmlElement&); | |||||
/** Creates a (deep) copy of another element. */ | |||||
XmlElement& operator= (const XmlElement&); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
XmlElement (XmlElement&&) noexcept; | |||||
XmlElement& operator= (XmlElement&&) noexcept; | |||||
#endif | |||||
/** Deleting an XmlElement will also delete all of its child elements. */ | |||||
~XmlElement() noexcept; | |||||
//============================================================================== | |||||
/** Compares two XmlElements to see if they contain the same text and attiributes. | |||||
The elements are only considered equivalent if they contain the same attiributes | |||||
with the same values, and have the same sub-nodes. | |||||
@param other the other element to compare to | |||||
@param ignoreOrderOfAttributes if true, this means that two elements with the | |||||
same attributes in a different order will be | |||||
considered the same; if false, the attributes must | |||||
be in the same order as well | |||||
*/ | |||||
bool isEquivalentTo (const XmlElement* other, | |||||
bool ignoreOrderOfAttributes) const noexcept; | |||||
//============================================================================== | |||||
/** Returns an XML text document that represents this element. | |||||
The string returned can be parsed to recreate the same XmlElement that | |||||
was used to create it. | |||||
@param dtdToUse the DTD to add to the document | |||||
@param allOnOneLine if true, this means that the document will not contain any | |||||
linefeeds, so it'll be smaller but not very easy to read. | |||||
@param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the | |||||
document | |||||
@param encodingType the character encoding format string to put into the xml | |||||
header | |||||
@param lineWrapLength the line length that will be used before items get placed on | |||||
a new line. This isn't an absolute maximum length, it just | |||||
determines how lists of attributes get broken up | |||||
@see writeToStream, writeToFile | |||||
*/ | |||||
String createDocument (StringRef dtdToUse, | |||||
bool allOnOneLine = false, | |||||
bool includeXmlHeader = true, | |||||
StringRef encodingType = "UTF-8", | |||||
int lineWrapLength = 60) const; | |||||
/** Writes the document to a stream as UTF-8. | |||||
@param output the stream to write to | |||||
@param dtdToUse the DTD to add to the document | |||||
@param allOnOneLine if true, this means that the document will not contain any | |||||
linefeeds, so it'll be smaller but not very easy to read. | |||||
@param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the | |||||
document | |||||
@param encodingType the character encoding format string to put into the xml | |||||
header | |||||
@param lineWrapLength the line length that will be used before items get placed on | |||||
a new line. This isn't an absolute maximum length, it just | |||||
determines how lists of attributes get broken up | |||||
@see writeToFile, createDocument | |||||
*/ | |||||
void writeToStream (OutputStream& output, | |||||
StringRef dtdToUse, | |||||
bool allOnOneLine = false, | |||||
bool includeXmlHeader = true, | |||||
StringRef encodingType = "UTF-8", | |||||
int lineWrapLength = 60) const; | |||||
/** Writes the element to a file as an XML document. | |||||
To improve safety in case something goes wrong while writing the file, this | |||||
will actually write the document to a new temporary file in the same | |||||
directory as the destination file, and if this succeeds, it will rename this | |||||
new file as the destination file (overwriting any existing file that was there). | |||||
@param destinationFile the file to write to. If this already exists, it will be | |||||
overwritten. | |||||
@param dtdToUse the DTD to add to the document | |||||
@param encodingType the character encoding format string to put into the xml | |||||
header | |||||
@param lineWrapLength the line length that will be used before items get placed on | |||||
a new line. This isn't an absolute maximum length, it just | |||||
determines how lists of attributes get broken up | |||||
@returns true if the file is written successfully; false if something goes wrong | |||||
in the process | |||||
@see createDocument | |||||
*/ | |||||
bool writeToFile (const File& destinationFile, | |||||
StringRef dtdToUse, | |||||
StringRef encodingType = "UTF-8", | |||||
int lineWrapLength = 60) const; | |||||
//============================================================================== | |||||
/** Returns this element's tag type name. | |||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would return "MOOSE". | |||||
@see hasTagName | |||||
*/ | |||||
const String& getTagName() const noexcept { return tagName; } | |||||
/** Returns the namespace portion of the tag-name, or an empty string if none is specified. */ | |||||
String getNamespace() const; | |||||
/** Returns the part of the tag-name that follows any namespace declaration. */ | |||||
String getTagNameWithoutNamespace() const; | |||||
/** Tests whether this element has a particular tag name. | |||||
@param possibleTagName the tag name you're comparing it with | |||||
@see getTagName | |||||
*/ | |||||
bool hasTagName (StringRef possibleTagName) const noexcept; | |||||
/** Tests whether this element has a particular tag name, ignoring any XML namespace prefix. | |||||
So a test for e.g. "xyz" will return true for "xyz" and also "foo:xyz", "bar::xyz", etc. | |||||
@see getTagName | |||||
*/ | |||||
bool hasTagNameIgnoringNamespace (StringRef possibleTagName) const; | |||||
//============================================================================== | |||||
/** Returns the number of XML attributes this element contains. | |||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would | |||||
return 2. | |||||
*/ | |||||
int getNumAttributes() const noexcept; | |||||
/** Returns the name of one of the elements attributes. | |||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, then | |||||
getAttributeName(1) would return "antlers". | |||||
@see getAttributeValue, getStringAttribute | |||||
*/ | |||||
const String& getAttributeName (int attributeIndex) const noexcept; | |||||
/** Returns the value of one of the elements attributes. | |||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, then | |||||
getAttributeName(1) would return "2". | |||||
@see getAttributeName, getStringAttribute | |||||
*/ | |||||
const String& getAttributeValue (int attributeIndex) const noexcept; | |||||
//============================================================================== | |||||
// Attribute-handling methods.. | |||||
/** Checks whether the element contains an attribute with a certain name. */ | |||||
bool hasAttribute (StringRef attributeName) const noexcept; | |||||
/** Returns the value of a named attribute. | |||||
@param attributeName the name of the attribute to look up | |||||
*/ | |||||
const String& getStringAttribute (StringRef attributeName) const noexcept; | |||||
/** Returns the value of a named attribute. | |||||
@param attributeName the name of the attribute to look up | |||||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||||
with this name | |||||
*/ | |||||
String getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const; | |||||
/** Compares the value of a named attribute with a value passed-in. | |||||
@param attributeName the name of the attribute to look up | |||||
@param stringToCompareAgainst the value to compare it with | |||||
@param ignoreCase whether the comparison should be case-insensitive | |||||
@returns true if the value of the attribute is the same as the string passed-in; | |||||
false if it's different (or if no such attribute exists) | |||||
*/ | |||||
bool compareAttribute (StringRef attributeName, | |||||
StringRef stringToCompareAgainst, | |||||
bool ignoreCase = false) const noexcept; | |||||
/** Returns the value of a named attribute as an integer. | |||||
This will try to find the attribute and convert it to an integer (using | |||||
the String::getIntValue() method). | |||||
@param attributeName the name of the attribute to look up | |||||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||||
with this name | |||||
@see setAttribute | |||||
*/ | |||||
int getIntAttribute (StringRef attributeName, int defaultReturnValue = 0) const; | |||||
/** Returns the value of a named attribute as floating-point. | |||||
This will try to find the attribute and convert it to a double (using | |||||
the String::getDoubleValue() method). | |||||
@param attributeName the name of the attribute to look up | |||||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||||
with this name | |||||
@see setAttribute | |||||
*/ | |||||
double getDoubleAttribute (StringRef attributeName, double defaultReturnValue = 0.0) const; | |||||
/** Returns the value of a named attribute as a boolean. | |||||
This will try to find the attribute and interpret it as a boolean. To do this, | |||||
it'll return true if the value is "1", "true", "y", etc, or false for other | |||||
values. | |||||
@param attributeName the name of the attribute to look up | |||||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||||
with this name | |||||
*/ | |||||
bool getBoolAttribute (StringRef attributeName, bool defaultReturnValue = false) const; | |||||
/** Adds a named attribute to the element. | |||||
If the element already contains an attribute with this name, it's value will | |||||
be updated to the new value. If there's no such attribute yet, a new one will | |||||
be added. | |||||
Note that there are other setAttribute() methods that take integers, | |||||
doubles, etc. to make it easy to store numbers. | |||||
@param attributeName the name of the attribute to set | |||||
@param newValue the value to set it to | |||||
@see removeAttribute | |||||
*/ | |||||
void setAttribute (const Identifier& attributeName, const String& newValue); | |||||
/** Adds a named attribute to the element, setting it to an integer value. | |||||
If the element already contains an attribute with this name, it's value will | |||||
be updated to the new value. If there's no such attribute yet, a new one will | |||||
be added. | |||||
Note that there are other setAttribute() methods that take integers, | |||||
doubles, etc. to make it easy to store numbers. | |||||
@param attributeName the name of the attribute to set | |||||
@param newValue the value to set it to | |||||
*/ | |||||
void setAttribute (const Identifier& attributeName, int newValue); | |||||
/** Adds a named attribute to the element, setting it to a floating-point value. | |||||
If the element already contains an attribute with this name, it's value will | |||||
be updated to the new value. If there's no such attribute yet, a new one will | |||||
be added. | |||||
Note that there are other setAttribute() methods that take integers, | |||||
doubles, etc. to make it easy to store numbers. | |||||
@param attributeName the name of the attribute to set | |||||
@param newValue the value to set it to | |||||
*/ | |||||
void setAttribute (const Identifier& attributeName, double newValue); | |||||
/** Removes a named attribute from the element. | |||||
@param attributeName the name of the attribute to remove | |||||
@see removeAllAttributes | |||||
*/ | |||||
void removeAttribute (const Identifier& attributeName) noexcept; | |||||
/** Removes all attributes from this element. */ | |||||
void removeAllAttributes() noexcept; | |||||
//============================================================================== | |||||
// Child element methods.. | |||||
/** Returns the first of this element's sub-elements. | |||||
see getNextElement() for an example of how to iterate the sub-elements. | |||||
@see forEachXmlChildElement | |||||
*/ | |||||
XmlElement* getFirstChildElement() const noexcept { return firstChildElement; } | |||||
/** Returns the next of this element's siblings. | |||||
This can be used for iterating an element's sub-elements, e.g. | |||||
@code | |||||
XmlElement* child = myXmlDocument->getFirstChildElement(); | |||||
while (child != nullptr) | |||||
{ | |||||
...do stuff with this child.. | |||||
child = child->getNextElement(); | |||||
} | |||||
@endcode | |||||
Note that when iterating the child elements, some of them might be | |||||
text elements as well as XML tags - use isTextElement() to work this | |||||
out. | |||||
Also, it's much easier and neater to use this method indirectly via the | |||||
forEachXmlChildElement macro. | |||||
@returns the sibling element that follows this one, or a nullptr if | |||||
this is the last element in its parent | |||||
@see getNextElement, isTextElement, forEachXmlChildElement | |||||
*/ | |||||
inline XmlElement* getNextElement() const noexcept { return nextListItem; } | |||||
/** Returns the next of this element's siblings which has the specified tag | |||||
name. | |||||
This is like getNextElement(), but will scan through the list until it | |||||
finds an element with the given tag name. | |||||
@see getNextElement, forEachXmlChildElementWithTagName | |||||
*/ | |||||
XmlElement* getNextElementWithTagName (StringRef requiredTagName) const; | |||||
/** Returns the number of sub-elements in this element. | |||||
@see getChildElement | |||||
*/ | |||||
int getNumChildElements() const noexcept; | |||||
/** Returns the sub-element at a certain index. | |||||
It's not very efficient to iterate the sub-elements by index - see | |||||
getNextElement() for an example of how best to iterate. | |||||
@returns the n'th child of this element, or nullptr if the index is out-of-range | |||||
@see getNextElement, isTextElement, getChildByName | |||||
*/ | |||||
XmlElement* getChildElement (int index) const noexcept; | |||||
/** Returns the first sub-element with a given tag-name. | |||||
@param tagNameToLookFor the tag name of the element you want to find | |||||
@returns the first element with this tag name, or nullptr if none is found | |||||
@see getNextElement, isTextElement, getChildElement, getChildByAttribute | |||||
*/ | |||||
XmlElement* getChildByName (StringRef tagNameToLookFor) const noexcept; | |||||
/** Returns the first sub-element which has an attribute that matches the given value. | |||||
@param attributeName the name of the attribute to check | |||||
@param attributeValue the target value of the attribute | |||||
@returns the first element with this attribute value, or nullptr if none is found | |||||
@see getChildByName | |||||
*/ | |||||
XmlElement* getChildByAttribute (StringRef attributeName, | |||||
StringRef attributeValue) const noexcept; | |||||
//============================================================================== | |||||
/** Appends an element to this element's list of children. | |||||
Child elements are deleted automatically when their parent is deleted, so | |||||
make sure the object that you pass in will not be deleted by anything else, | |||||
and make sure it's not already the child of another element. | |||||
Note that due to the XmlElement using a singly-linked-list, prependChildElement() | |||||
is an O(1) operation, but addChildElement() is an O(N) operation - so if | |||||
you're adding large number of elements, you may prefer to do so in reverse order! | |||||
@see getFirstChildElement, getNextElement, getNumChildElements, | |||||
getChildElement, removeChildElement | |||||
*/ | |||||
void addChildElement (XmlElement* newChildElement) noexcept; | |||||
/** Inserts an element into this element's list of children. | |||||
Child elements are deleted automatically when their parent is deleted, so | |||||
make sure the object that you pass in will not be deleted by anything else, | |||||
and make sure it's not already the child of another element. | |||||
@param newChildElement the element to add | |||||
@param indexToInsertAt the index at which to insert the new element - if this is | |||||
below zero, it will be added to the end of the list | |||||
@see addChildElement, insertChildElement | |||||
*/ | |||||
void insertChildElement (XmlElement* newChildElement, | |||||
int indexToInsertAt) noexcept; | |||||
/** Inserts an element at the beginning of this element's list of children. | |||||
Child elements are deleted automatically when their parent is deleted, so | |||||
make sure the object that you pass in will not be deleted by anything else, | |||||
and make sure it's not already the child of another element. | |||||
Note that due to the XmlElement using a singly-linked-list, prependChildElement() | |||||
is an O(1) operation, but addChildElement() is an O(N) operation - so if | |||||
you're adding large number of elements, you may prefer to do so in reverse order! | |||||
@see addChildElement, insertChildElement | |||||
*/ | |||||
void prependChildElement (XmlElement* newChildElement) noexcept; | |||||
/** Creates a new element with the given name and returns it, after adding it | |||||
as a child element. | |||||
This is a handy method that means that instead of writing this: | |||||
@code | |||||
XmlElement* newElement = new XmlElement ("foobar"); | |||||
myParentElement->addChildElement (newElement); | |||||
@endcode | |||||
..you could just write this: | |||||
@code | |||||
XmlElement* newElement = myParentElement->createNewChildElement ("foobar"); | |||||
@endcode | |||||
*/ | |||||
XmlElement* createNewChildElement (StringRef tagName); | |||||
/** Replaces one of this element's children with another node. | |||||
If the current element passed-in isn't actually a child of this element, | |||||
this will return false and the new one won't be added. Otherwise, the | |||||
existing element will be deleted, replaced with the new one, and it | |||||
will return true. | |||||
*/ | |||||
bool replaceChildElement (XmlElement* currentChildElement, | |||||
XmlElement* newChildNode) noexcept; | |||||
/** Removes a child element. | |||||
@param childToRemove the child to look for and remove | |||||
@param shouldDeleteTheChild if true, the child will be deleted, if false it'll | |||||
just remove it | |||||
*/ | |||||
void removeChildElement (XmlElement* childToRemove, | |||||
bool shouldDeleteTheChild) noexcept; | |||||
/** Deletes all the child elements in the element. | |||||
@see removeChildElement, deleteAllChildElementsWithTagName | |||||
*/ | |||||
void deleteAllChildElements() noexcept; | |||||
/** Deletes all the child elements with a given tag name. | |||||
@see removeChildElement | |||||
*/ | |||||
void deleteAllChildElementsWithTagName (StringRef tagName) noexcept; | |||||
/** Returns true if the given element is a child of this one. */ | |||||
bool containsChildElement (const XmlElement* possibleChild) const noexcept; | |||||
/** Recursively searches all sub-elements of this one, looking for an element | |||||
which is the direct parent of the specified element. | |||||
Because elements don't store a pointer to their parent, if you have one | |||||
and need to find its parent, the only way to do so is to exhaustively | |||||
search the whole tree for it. | |||||
If the given child is found somewhere in this element's hierarchy, then | |||||
this method will return its parent. If not, it will return nullptr. | |||||
*/ | |||||
XmlElement* findParentElementOf (const XmlElement* childToSearchFor) noexcept; | |||||
//============================================================================== | |||||
/** Sorts the child elements using a comparator. | |||||
This will use a comparator object to sort the elements into order. The object | |||||
passed must have a method of the form: | |||||
@code | |||||
int compareElements (const XmlElement* first, const XmlElement* second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator the comparator to use for comparing elements. | |||||
@param retainOrderOfEquivalentItems if this is true, then items which the comparator | |||||
says are equivalent will be kept in the order in which they | |||||
currently appear in the array. This is slower to perform, but | |||||
may be important in some cases. If it's false, a faster algorithm | |||||
is used, but equivalent elements may be rearranged. | |||||
*/ | |||||
template <class ElementComparator> | |||||
void sortChildElements (ElementComparator& comparator, | |||||
bool retainOrderOfEquivalentItems = false) | |||||
{ | |||||
const int num = getNumChildElements(); | |||||
if (num > 1) | |||||
{ | |||||
HeapBlock<XmlElement*> elems ((size_t) num); | |||||
getChildElementsAsArray (elems); | |||||
sortArray (comparator, (XmlElement**) elems, 0, num - 1, retainOrderOfEquivalentItems); | |||||
reorderChildElements (elems, num); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Returns true if this element is a section of text. | |||||
Elements can either be an XML tag element or a secton of text, so this | |||||
is used to find out what kind of element this one is. | |||||
@see getAllText, addTextElement, deleteAllTextElements | |||||
*/ | |||||
bool isTextElement() const noexcept; | |||||
/** Returns the text for a text element. | |||||
Note that if you have an element like this: | |||||
@code<xyz>hello</xyz>@endcode | |||||
then calling getText on the "xyz" element won't return "hello", because that is | |||||
actually stored in a special text sub-element inside the xyz element. To get the | |||||
"hello" string, you could either call getText on the (unnamed) sub-element, or | |||||
use getAllSubText() to do this automatically. | |||||
Note that leading and trailing whitespace will be included in the string - to remove | |||||
if, just call String::trim() on the result. | |||||
@see isTextElement, getAllSubText, getChildElementAllSubText | |||||
*/ | |||||
const String& getText() const noexcept; | |||||
/** Sets the text in a text element. | |||||
Note that this is only a valid call if this element is a text element. If it's | |||||
not, then no action will be performed. If you're trying to add text inside a normal | |||||
element, you probably want to use addTextElement() instead. | |||||
*/ | |||||
void setText (const String& newText); | |||||
/** Returns all the text from this element's child nodes. | |||||
This iterates all the child elements and when it finds text elements, | |||||
it concatenates their text into a big string which it returns. | |||||
E.g. @code<xyz>hello <x>there</x> world</xyz>@endcode | |||||
if you called getAllSubText on the "xyz" element, it'd return "hello there world". | |||||
Note that leading and trailing whitespace will be included in the string - to remove | |||||
if, just call String::trim() on the result. | |||||
@see isTextElement, getChildElementAllSubText, getText, addTextElement | |||||
*/ | |||||
String getAllSubText() const; | |||||
/** Returns all the sub-text of a named child element. | |||||
If there is a child element with the given tag name, this will return | |||||
all of its sub-text (by calling getAllSubText() on it). If there is | |||||
no such child element, this will return the default string passed-in. | |||||
@see getAllSubText | |||||
*/ | |||||
String getChildElementAllSubText (StringRef childTagName, | |||||
const String& defaultReturnValue) const; | |||||
/** Appends a section of text to this element. | |||||
@see isTextElement, getText, getAllSubText | |||||
*/ | |||||
void addTextElement (const String& text); | |||||
/** Removes all the text elements from this element. | |||||
@see isTextElement, getText, getAllSubText, addTextElement | |||||
*/ | |||||
void deleteAllTextElements() noexcept; | |||||
/** Creates a text element that can be added to a parent element. */ | |||||
static XmlElement* createTextElement (const String& text); | |||||
/** Checks if a given string is a valid XML name */ | |||||
static bool isValidXmlName (StringRef possibleName) noexcept; | |||||
//============================================================================== | |||||
private: | |||||
struct XmlAttributeNode | |||||
{ | |||||
XmlAttributeNode (const XmlAttributeNode&) noexcept; | |||||
XmlAttributeNode (const Identifier&, const String&) noexcept; | |||||
XmlAttributeNode (String::CharPointerType, String::CharPointerType); | |||||
LinkedListPointer<XmlAttributeNode> nextListItem; | |||||
Identifier name; | |||||
String value; | |||||
private: | |||||
XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION; | |||||
}; | |||||
friend class XmlDocument; | |||||
friend class LinkedListPointer<XmlAttributeNode>; | |||||
friend class LinkedListPointer<XmlElement>; | |||||
friend class LinkedListPointer<XmlElement>::Appender; | |||||
friend class NamedValueSet; | |||||
LinkedListPointer<XmlElement> nextListItem; | |||||
LinkedListPointer<XmlElement> firstChildElement; | |||||
LinkedListPointer<XmlAttributeNode> attributes; | |||||
String tagName; | |||||
XmlElement (int) noexcept; | |||||
void copyChildrenAndAttributesFrom (const XmlElement&); | |||||
void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const; | |||||
void getChildElementsAsArray (XmlElement**) const noexcept; | |||||
void reorderChildElements (XmlElement**, int) noexcept; | |||||
XmlAttributeNode* getAttribute (StringRef) const noexcept; | |||||
// Sigh.. L"" or _T("") string literals are problematic in general, and really inappropriate | |||||
// for XML tags. Use a UTF-8 encoded literal instead, or if you're really determined to use | |||||
// UTF-16, cast it to a String and use the other constructor. | |||||
XmlElement (const wchar_t*) JUCE_DELETED_FUNCTION; | |||||
JUCE_LEAK_DETECTOR (XmlElement) | |||||
}; | |||||
#endif // JUCE_XMLELEMENT_H_INCLUDED |