@@ -199,3 +199,6 @@ data/macos/*.*/ | |||
data/macos/Carla/ | |||
data/windows/python/ | |||
source/modules/juce_audio_graph/test | |||
source/modules/juce_audio_graph/test.d |
@@ -569,7 +569,7 @@ public: | |||
{ | |||
float* const d = channels [channel] + startSample; | |||
if (gain == 0.0f) | |||
if (carla_isZero (gain)) | |||
carla_zeroFloats (d, numSamples); | |||
else | |||
carla_multiply (d, gain, numSamples); | |||
@@ -0,0 +1,373 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_LINKEDLISTPOINTER_H_INCLUDED | |||
#define JUCE_LINKEDLISTPOINTER_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Helps to manipulate singly-linked lists of objects. | |||
For objects that are designed to contain a pointer to the subsequent item in the | |||
list, this class contains methods to deal with the list. To use it, the ObjectType | |||
class that it points to must contain a LinkedListPointer called nextListItem, e.g. | |||
@code | |||
struct MyObject | |||
{ | |||
int x, y, z; | |||
// A linkable object must contain a member with this name and type, which must be | |||
// accessible by the LinkedListPointer class. (This doesn't mean it has to be public - | |||
// you could make your class a friend of a LinkedListPointer<MyObject> instead). | |||
LinkedListPointer<MyObject> nextListItem; | |||
}; | |||
LinkedListPointer<MyObject> myList; | |||
myList.append (new MyObject()); | |||
myList.append (new MyObject()); | |||
int numItems = myList.size(); // returns 2 | |||
MyObject* lastInList = myList.getLast(); | |||
@endcode | |||
*/ | |||
template <class ObjectType> | |||
class LinkedListPointer | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a null pointer to an empty list. */ | |||
LinkedListPointer() noexcept | |||
: item (nullptr) | |||
{ | |||
} | |||
/** Creates a pointer to a list whose head is the item provided. */ | |||
explicit LinkedListPointer (ObjectType* const headItem) noexcept | |||
: item (headItem) | |||
{ | |||
} | |||
/** Sets this pointer to point to a new list. */ | |||
LinkedListPointer& operator= (ObjectType* const newItem) noexcept | |||
{ | |||
item = newItem; | |||
return *this; | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
LinkedListPointer (LinkedListPointer&& other) noexcept | |||
: item (other.item) | |||
{ | |||
other.item = nullptr; | |||
} | |||
LinkedListPointer& operator= (LinkedListPointer&& other) noexcept | |||
{ | |||
jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||
item = other.item; | |||
other.item = nullptr; | |||
return *this; | |||
} | |||
#endif | |||
//============================================================================== | |||
/** Returns the item which this pointer points to. */ | |||
inline operator ObjectType*() const noexcept | |||
{ | |||
return item; | |||
} | |||
/** Returns the item which this pointer points to. */ | |||
inline ObjectType* get() const noexcept | |||
{ | |||
return item; | |||
} | |||
/** Returns the last item in the list which this pointer points to. | |||
This will iterate the list and return the last item found. Obviously the speed | |||
of this operation will be proportional to the size of the list. If the list is | |||
empty the return value will be this object. | |||
If you're planning on appending a number of items to your list, it's much more | |||
efficient to use the Appender class than to repeatedly call getLast() to find the end. | |||
*/ | |||
LinkedListPointer& getLast() noexcept | |||
{ | |||
LinkedListPointer* l = this; | |||
while (l->item != nullptr) | |||
l = &(l->item->nextListItem); | |||
return *l; | |||
} | |||
/** Returns the number of items in the list. | |||
Obviously with a simple linked list, getting the size involves iterating the list, so | |||
this can be a lengthy operation - be careful when using this method in your code. | |||
*/ | |||
int size() const noexcept | |||
{ | |||
int total = 0; | |||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||
++total; | |||
return total; | |||
} | |||
/** Returns the item at a given index in the list. | |||
Since the only way to find an item is to iterate the list, this operation can obviously | |||
be slow, depending on its size, so you should be careful when using this in algorithms. | |||
*/ | |||
LinkedListPointer& operator[] (int index) noexcept | |||
{ | |||
LinkedListPointer* l = this; | |||
while (--index >= 0 && l->item != nullptr) | |||
l = &(l->item->nextListItem); | |||
return *l; | |||
} | |||
/** Returns the item at a given index in the list. | |||
Since the only way to find an item is to iterate the list, this operation can obviously | |||
be slow, depending on its size, so you should be careful when using this in algorithms. | |||
*/ | |||
const LinkedListPointer& operator[] (int index) const noexcept | |||
{ | |||
const LinkedListPointer* l = this; | |||
while (--index >= 0 && l->item != nullptr) | |||
l = &(l->item->nextListItem); | |||
return *l; | |||
} | |||
/** Returns true if the list contains the given item. */ | |||
bool contains (const ObjectType* const itemToLookFor) const noexcept | |||
{ | |||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||
if (itemToLookFor == i) | |||
return true; | |||
return false; | |||
} | |||
//============================================================================== | |||
/** Inserts an item into the list, placing it before the item that this pointer | |||
currently points to. | |||
*/ | |||
void insertNext (ObjectType* const newItem) | |||
{ | |||
jassert (newItem != nullptr); | |||
jassert (newItem->nextListItem == nullptr); | |||
newItem->nextListItem = item; | |||
item = newItem; | |||
} | |||
/** Inserts an item at a numeric index in the list. | |||
Obviously this will involve iterating the list to find the item at the given index, | |||
so be careful about the impact this may have on execution time. | |||
*/ | |||
void insertAtIndex (int index, ObjectType* newItem) | |||
{ | |||
jassert (newItem != nullptr); | |||
LinkedListPointer* l = this; | |||
while (index != 0 && l->item != nullptr) | |||
{ | |||
l = &(l->item->nextListItem); | |||
--index; | |||
} | |||
l->insertNext (newItem); | |||
} | |||
/** Replaces the object that this pointer points to, appending the rest of the list to | |||
the new object, and returning the old one. | |||
*/ | |||
ObjectType* replaceNext (ObjectType* const newItem) noexcept | |||
{ | |||
jassert (newItem != nullptr); | |||
jassert (newItem->nextListItem == nullptr); | |||
ObjectType* const oldItem = item; | |||
item = newItem; | |||
item->nextListItem = oldItem->nextListItem.item; | |||
oldItem->nextListItem.item = nullptr; | |||
return oldItem; | |||
} | |||
/** Adds an item to the end of the list. | |||
This operation involves iterating the whole list, so can be slow - if you need to | |||
append a number of items to your list, it's much more efficient to use the Appender | |||
class than to repeatedly call append(). | |||
*/ | |||
void append (ObjectType* const newItem) | |||
{ | |||
getLast().item = newItem; | |||
} | |||
/** Creates copies of all the items in another list and adds them to this one. | |||
This will use the ObjectType's copy constructor to try to create copies of each | |||
item in the other list, and appends them to this list. | |||
*/ | |||
void addCopyOfList (const LinkedListPointer& other) | |||
{ | |||
LinkedListPointer* insertPoint = this; | |||
for (ObjectType* i = other.item; i != nullptr; i = i->nextListItem) | |||
{ | |||
insertPoint->insertNext (new ObjectType (*i)); | |||
insertPoint = &(insertPoint->item->nextListItem); | |||
} | |||
} | |||
/** Removes the head item from the list. | |||
This won't delete the object that is removed, but returns it, so the caller can | |||
delete it if necessary. | |||
*/ | |||
ObjectType* removeNext() noexcept | |||
{ | |||
ObjectType* const oldItem = item; | |||
if (oldItem != nullptr) | |||
{ | |||
item = oldItem->nextListItem; | |||
oldItem->nextListItem.item = nullptr; | |||
} | |||
return oldItem; | |||
} | |||
/** Removes a specific item from the list. | |||
Note that this will not delete the item, it simply unlinks it from the list. | |||
*/ | |||
void remove (ObjectType* const itemToRemove) | |||
{ | |||
if (LinkedListPointer* const l = findPointerTo (itemToRemove)) | |||
l->removeNext(); | |||
} | |||
/** Iterates the list, calling the delete operator on all of its elements and | |||
leaving this pointer empty. | |||
*/ | |||
void deleteAll() | |||
{ | |||
while (item != nullptr) | |||
{ | |||
ObjectType* const oldItem = item; | |||
item = oldItem->nextListItem; | |||
delete oldItem; | |||
} | |||
} | |||
/** Finds a pointer to a given item. | |||
If the item is found in the list, this returns the pointer that points to it. If | |||
the item isn't found, this returns null. | |||
*/ | |||
LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept | |||
{ | |||
LinkedListPointer* l = this; | |||
while (l->item != nullptr) | |||
{ | |||
if (l->item == itemToLookFor) | |||
return l; | |||
l = &(l->item->nextListItem); | |||
} | |||
return nullptr; | |||
} | |||
/** Copies the items in the list to an array. | |||
The destArray must contain enough elements to hold the entire list - no checks are | |||
made for this! | |||
*/ | |||
void copyToArray (ObjectType** destArray) const noexcept | |||
{ | |||
jassert (destArray != nullptr); | |||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||
*destArray++ = i; | |||
} | |||
/** Swaps this pointer with another one */ | |||
void swapWith (LinkedListPointer& other) noexcept | |||
{ | |||
std::swap (item, other.item); | |||
} | |||
//============================================================================== | |||
/** | |||
Allows efficient repeated insertions into a list. | |||
You can create an Appender object which points to the last element in your | |||
list, and then repeatedly call Appender::append() to add items to the end | |||
of the list in O(1) time. | |||
*/ | |||
class Appender | |||
{ | |||
public: | |||
/** Creates an appender which will add items to the given list. | |||
*/ | |||
Appender (LinkedListPointer& endOfListPointer) noexcept | |||
: endOfList (&endOfListPointer) | |||
{ | |||
// This can only be used to add to the end of a list. | |||
jassert (endOfListPointer.item == nullptr); | |||
} | |||
/** Appends an item to the list. */ | |||
void append (ObjectType* const newItem) noexcept | |||
{ | |||
*endOfList = newItem; | |||
endOfList = &(newItem->nextListItem); | |||
} | |||
private: | |||
LinkedListPointer* endOfList; | |||
JUCE_DECLARE_NON_COPYABLE (Appender) | |||
}; | |||
private: | |||
//============================================================================== | |||
ObjectType* item; | |||
JUCE_DECLARE_NON_COPYABLE (LinkedListPointer) | |||
}; | |||
#endif // JUCE_LINKEDLISTPOINTER_H_INCLUDED |
@@ -0,0 +1,856 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||
#define JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Holds a list of objects derived from ReferenceCountedObject, or which implement basic | |||
reference-count handling methods. | |||
The template parameter specifies the class of the object you want to point to - the easiest | |||
way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject | |||
or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||
class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||
decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||
should behave. | |||
A ReferenceCountedArray holds objects derived from ReferenceCountedObject, | |||
and takes care of incrementing and decrementing their ref counts when they | |||
are added and removed from the array. | |||
To make all the array's methods thread-safe, pass in "CriticalSection" as the templated | |||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||
@see Array, OwnedArray, StringArray | |||
*/ | |||
template <class ObjectClass> | |||
class ReferenceCountedArray | |||
{ | |||
public: | |||
typedef ReferenceCountedObjectPtr<ObjectClass> ObjectClassPtr; | |||
//============================================================================== | |||
/** Creates an empty array. | |||
@see ReferenceCountedObject, Array, OwnedArray | |||
*/ | |||
ReferenceCountedArray() noexcept | |||
: numUsed (0) | |||
{ | |||
} | |||
/** Creates a copy of another array */ | |||
ReferenceCountedArray (const ReferenceCountedArray& other) noexcept | |||
{ | |||
numUsed = other.size(); | |||
data.setAllocatedSize (numUsed); | |||
memcpy (data.elements, other.getRawDataPointer(), (size_t) numUsed * sizeof (ObjectClass*)); | |||
for (int i = numUsed; --i >= 0;) | |||
if (ObjectClass* o = data.elements[i]) | |||
o->incReferenceCount(); | |||
} | |||
/** Creates a copy of another array */ | |||
template <class OtherObjectClass> | |||
ReferenceCountedArray (const ReferenceCountedArray<OtherObjectClass>& other) noexcept | |||
{ | |||
numUsed = other.size(); | |||
data.setAllocatedSize (numUsed); | |||
memcpy (data.elements, other.getRawDataPointer(), numUsed * sizeof (ObjectClass*)); | |||
for (int i = numUsed; --i >= 0;) | |||
if (ObjectClass* o = data.elements[i]) | |||
o->incReferenceCount(); | |||
} | |||
/** Copies another array into this one. | |||
Any existing objects in this array will first be released. | |||
*/ | |||
ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept | |||
{ | |||
ReferenceCountedArray otherCopy (other); | |||
swapWith (otherCopy); | |||
return *this; | |||
} | |||
/** Copies another array into this one. | |||
Any existing objects in this array will first be released. | |||
*/ | |||
template <class OtherObjectClass> | |||
ReferenceCountedArray<ObjectClass>& operator= (const ReferenceCountedArray<OtherObjectClass>& other) noexcept | |||
{ | |||
ReferenceCountedArray<ObjectClass> otherCopy (other); | |||
swapWith (otherCopy); | |||
return *this; | |||
} | |||
/** Destructor. | |||
Any objects in the array will be released, and may be deleted if not referenced from elsewhere. | |||
*/ | |||
~ReferenceCountedArray() | |||
{ | |||
releaseAllObjects(); | |||
} | |||
//============================================================================== | |||
/** Removes all objects from the array. | |||
Any objects in the array that whose reference counts drop to zero will be deleted. | |||
*/ | |||
void clear() | |||
{ | |||
releaseAllObjects(); | |||
data.setAllocatedSize (0); | |||
} | |||
/** Removes all objects from the array without freeing the array's allocated storage. | |||
Any objects in the array that whose reference counts drop to zero will be deleted. | |||
@see clear | |||
*/ | |||
void clearQuick() | |||
{ | |||
releaseAllObjects(); | |||
} | |||
/** Returns the current number of objects in the array. */ | |||
inline int size() const noexcept | |||
{ | |||
return numUsed; | |||
} | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
/** Returns a pointer to the object at this index in the array. | |||
If the index is out-of-range, this will return a null pointer, (and | |||
it could be null anyway, because it's ok for the array to hold null | |||
pointers as well as objects). | |||
@see getUnchecked | |||
*/ | |||
inline ObjectClassPtr operator[] (const int index) const noexcept | |||
{ | |||
return getObjectPointer (index); | |||
} | |||
/** Returns a pointer to the object at this index in the array, without checking | |||
whether the index is in-range. | |||
This is a faster and less safe version of operator[] which doesn't check the index passed in, so | |||
it can be used when you're sure the index is always going to be legal. | |||
*/ | |||
inline ObjectClassPtr getUnchecked (const int index) const noexcept | |||
{ | |||
return getObjectPointerUnchecked (index); | |||
} | |||
/** Returns a raw pointer to the object at this index in the array. | |||
If the index is out-of-range, this will return a null pointer, (and | |||
it could be null anyway, because it's ok for the array to hold null | |||
pointers as well as objects). | |||
@see getUnchecked | |||
*/ | |||
inline ObjectClass* getObjectPointer (const int index) const noexcept | |||
{ | |||
if (isPositiveAndBelow (index, numUsed)) | |||
{ | |||
jassert (data.elements != nullptr); | |||
return data.elements [index]; | |||
} | |||
return ObjectClassPtr(); | |||
} | |||
/** Returns a raw pointer to the object at this index in the array, without checking | |||
whether the index is in-range. | |||
*/ | |||
inline ObjectClass* getObjectPointerUnchecked (const int index) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); | |||
return data.elements [index]; | |||
} | |||
/** Returns a pointer to the first object in the array. | |||
This will return a null pointer if the array's empty. | |||
@see getLast | |||
*/ | |||
inline ObjectClassPtr getFirst() const noexcept | |||
{ | |||
if (numUsed > 0) | |||
{ | |||
jassert (data.elements != nullptr); | |||
return data.elements [0]; | |||
} | |||
return ObjectClassPtr(); | |||
} | |||
/** Returns a pointer to the last object in the array. | |||
This will return a null pointer if the array's empty. | |||
@see getFirst | |||
*/ | |||
inline ObjectClassPtr getLast() const noexcept | |||
{ | |||
if (numUsed > 0) | |||
{ | |||
jassert (data.elements != nullptr); | |||
return data.elements [numUsed - 1]; | |||
} | |||
return ObjectClassPtr(); | |||
} | |||
/** Returns a pointer to the actual array data. | |||
This pointer will only be valid until the next time a non-const method | |||
is called on the array. | |||
*/ | |||
inline ObjectClass** getRawDataPointer() const noexcept | |||
{ | |||
return data.elements; | |||
} | |||
//============================================================================== | |||
/** Returns a pointer to the first element in the array. | |||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||
*/ | |||
inline ObjectClass** begin() const noexcept | |||
{ | |||
return data.elements; | |||
} | |||
/** Returns a pointer to the element which follows the last element in the array. | |||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||
*/ | |||
inline ObjectClass** end() const noexcept | |||
{ | |||
return data.elements + numUsed; | |||
} | |||
//============================================================================== | |||
/** Finds the index of the first occurrence of an object in the array. | |||
@param objectToLookFor the object to look for | |||
@returns the index at which the object was found, or -1 if it's not found | |||
*/ | |||
int indexOf (const ObjectClass* const objectToLookFor) const noexcept | |||
{ | |||
ObjectClass** e = data.elements.getData(); | |||
ObjectClass** const endPointer = e + numUsed; | |||
while (e != endPointer) | |||
{ | |||
if (objectToLookFor == *e) | |||
return static_cast<int> (e - data.elements.getData()); | |||
++e; | |||
} | |||
return -1; | |||
} | |||
/** Returns true if the array contains a specified object. | |||
@param objectToLookFor the object to look for | |||
@returns true if the object is in the array | |||
*/ | |||
bool contains (const ObjectClass* const objectToLookFor) const noexcept | |||
{ | |||
ObjectClass** e = data.elements.getData(); | |||
ObjectClass** const endPointer = e + numUsed; | |||
while (e != endPointer) | |||
{ | |||
if (objectToLookFor == *e) | |||
return true; | |||
++e; | |||
} | |||
return false; | |||
} | |||
/** Appends a new object to the end of the array. | |||
This will increase the new object's reference count. | |||
@param newObject the new object to add to the array | |||
@see set, insert, addIfNotAlreadyThere, addSorted, addArray | |||
*/ | |||
ObjectClass* add (ObjectClass* const newObject) noexcept | |||
{ | |||
data.ensureAllocatedSize (numUsed + 1); | |||
jassert (data.elements != nullptr); | |||
data.elements [numUsed++] = newObject; | |||
if (newObject != nullptr) | |||
newObject->incReferenceCount(); | |||
return newObject; | |||
} | |||
/** Inserts a new object into the array at the given index. | |||
If the index is less than 0 or greater than the size of the array, the | |||
element will be added to the end of the array. | |||
Otherwise, it will be inserted into the array, moving all the later elements | |||
along to make room. | |||
This will increase the new object's reference count. | |||
@param indexToInsertAt the index at which the new element should be inserted | |||
@param newObject the new object to add to the array | |||
@see add, addSorted, addIfNotAlreadyThere, set | |||
*/ | |||
ObjectClass* insert (int indexToInsertAt, | |||
ObjectClass* const newObject) noexcept | |||
{ | |||
if (indexToInsertAt < 0) | |||
return add (newObject); | |||
if (indexToInsertAt > numUsed) | |||
indexToInsertAt = numUsed; | |||
data.ensureAllocatedSize (numUsed + 1); | |||
jassert (data.elements != nullptr); | |||
ObjectClass** const e = data.elements + indexToInsertAt; | |||
const int numToMove = numUsed - indexToInsertAt; | |||
if (numToMove > 0) | |||
memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); | |||
*e = newObject; | |||
if (newObject != nullptr) | |||
newObject->incReferenceCount(); | |||
++numUsed; | |||
return newObject; | |||
} | |||
/** Appends a new object at the end of the array as long as the array doesn't | |||
already contain it. | |||
If the array already contains a matching object, nothing will be done. | |||
@param newObject the new object to add to the array | |||
@returns true if the object has been added, false otherwise | |||
*/ | |||
bool addIfNotAlreadyThere (ObjectClass* const newObject) noexcept | |||
{ | |||
if (contains (newObject)) | |||
return false; | |||
add (newObject); | |||
return true; | |||
} | |||
/** Replaces an object in the array with a different one. | |||
If the index is less than zero, this method does nothing. | |||
If the index is beyond the end of the array, the new object is added to the end of the array. | |||
The object being added has its reference count increased, and if it's replacing | |||
another object, then that one has its reference count decreased, and may be deleted. | |||
@param indexToChange the index whose value you want to change | |||
@param newObject the new value to set for this index. | |||
@see add, insert, remove | |||
*/ | |||
void set (const int indexToChange, | |||
ObjectClass* const newObject) | |||
{ | |||
if (indexToChange >= 0) | |||
{ | |||
if (newObject != nullptr) | |||
newObject->incReferenceCount(); | |||
if (indexToChange < numUsed) | |||
{ | |||
if (ObjectClass* o = data.elements [indexToChange]) | |||
releaseObject (o); | |||
data.elements [indexToChange] = newObject; | |||
} | |||
else | |||
{ | |||
data.ensureAllocatedSize (numUsed + 1); | |||
jassert (data.elements != nullptr); | |||
data.elements [numUsed++] = newObject; | |||
} | |||
} | |||
} | |||
/** Adds elements from another array to the end of this array. | |||
@param arrayToAddFrom the array from which to copy the elements | |||
@param startIndex the first element of the other array to start copying from | |||
@param numElementsToAdd how many elements to add from the other array. If this | |||
value is negative or greater than the number of available elements, | |||
all available elements will be copied. | |||
@see add | |||
*/ | |||
void addArray (const ReferenceCountedArray<ObjectClass>& arrayToAddFrom, | |||
int startIndex = 0, | |||
int numElementsToAdd = -1) noexcept | |||
{ | |||
if (startIndex < 0) | |||
{ | |||
jassertfalse; | |||
startIndex = 0; | |||
} | |||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) | |||
numElementsToAdd = arrayToAddFrom.size() - startIndex; | |||
if (numElementsToAdd > 0) | |||
{ | |||
data.ensureAllocatedSize (numUsed + numElementsToAdd); | |||
while (--numElementsToAdd >= 0) | |||
add (arrayToAddFrom.getUnchecked (startIndex++)); | |||
} | |||
} | |||
/** Inserts a new object into the array assuming that the array is sorted. | |||
This will use a comparator to find the position at which the new object | |||
should go. If the array isn't sorted, the behaviour of this | |||
method will be unpredictable. | |||
@param comparator the comparator object to use to compare the elements - see the | |||
sort() method for details about this object's form | |||
@param newObject the new object to insert to the array | |||
@returns the index at which the new object was added | |||
@see add, sort | |||
*/ | |||
template <class ElementComparator> | |||
int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept | |||
{ | |||
const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); | |||
insert (index, newObject); | |||
return index; | |||
} | |||
/** Inserts or replaces an object in the array, assuming it is sorted. | |||
This is similar to addSorted, but if a matching element already exists, then it will be | |||
replaced by the new one, rather than the new one being added as well. | |||
*/ | |||
template <class ElementComparator> | |||
void addOrReplaceSorted (ElementComparator& comparator, | |||
ObjectClass* newObject) noexcept | |||
{ | |||
const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); | |||
if (index > 0 && comparator.compareElements (newObject, data.elements [index - 1]) == 0) | |||
set (index - 1, newObject); // replace an existing object that matches | |||
else | |||
insert (index, newObject); // no match, so insert the new one | |||
} | |||
/** Finds the index of an object in the array, assuming that the array is sorted. | |||
This will use a comparator to do a binary-chop to find the index of the given | |||
element, if it exists. If the array isn't sorted, the behaviour of this | |||
method will be unpredictable. | |||
@param comparator the comparator to use to compare the elements - see the sort() | |||
method for details about the form this object should take | |||
@param objectToLookFor the object to search for | |||
@returns the index of the element, or -1 if it's not found | |||
@see addSorted, sort | |||
*/ | |||
template <class ElementComparator> | |||
int indexOfSorted (ElementComparator& comparator, | |||
const ObjectClass* const objectToLookFor) const noexcept | |||
{ | |||
ignoreUnused (comparator); | |||
int s = 0, e = numUsed; | |||
while (s < e) | |||
{ | |||
if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) | |||
return s; | |||
const int halfway = (s + e) / 2; | |||
if (halfway == s) | |||
break; | |||
if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) | |||
s = halfway; | |||
else | |||
e = halfway; | |||
} | |||
return -1; | |||
} | |||
//============================================================================== | |||
/** Removes an object from the array. | |||
This will remove the object at a given index and move back all the | |||
subsequent objects to close the gap. | |||
If the index passed in is out-of-range, nothing will happen. | |||
The object that is removed will have its reference count decreased, | |||
and may be deleted if not referenced from elsewhere. | |||
@param indexToRemove the index of the element to remove | |||
@see removeObject, removeRange | |||
*/ | |||
void remove (const int indexToRemove) | |||
{ | |||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||
{ | |||
ObjectClass** const e = data.elements + indexToRemove; | |||
if (ObjectClass* o = *e) | |||
releaseObject (o); | |||
--numUsed; | |||
const int numberToShift = numUsed - indexToRemove; | |||
if (numberToShift > 0) | |||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); | |||
if ((numUsed << 1) < data.numAllocated) | |||
minimiseStorageOverheads(); | |||
} | |||
} | |||
/** Removes and returns an object from the array. | |||
This will remove the object at a given index and return it, moving back all | |||
the subsequent objects to close the gap. If the index passed in is out-of-range, | |||
nothing will happen and a null pointer will be returned. | |||
@param indexToRemove the index of the element to remove | |||
@see remove, removeObject, removeRange | |||
*/ | |||
ObjectClassPtr removeAndReturn (const int indexToRemove) | |||
{ | |||
ObjectClassPtr removedItem; | |||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||
{ | |||
ObjectClass** const e = data.elements + indexToRemove; | |||
if (ObjectClass* o = *e) | |||
{ | |||
removedItem = o; | |||
releaseObject (o); | |||
} | |||
--numUsed; | |||
const int numberToShift = numUsed - indexToRemove; | |||
if (numberToShift > 0) | |||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); | |||
if ((numUsed << 1) < data.numAllocated) | |||
minimiseStorageOverheads(); | |||
} | |||
return removedItem; | |||
} | |||
/** Removes the first occurrence of a specified object from the array. | |||
If the item isn't found, no action is taken. If it is found, it is | |||
removed and has its reference count decreased. | |||
@param objectToRemove the object to try to remove | |||
@see remove, removeRange | |||
*/ | |||
void removeObject (ObjectClass* const objectToRemove) | |||
{ | |||
remove (indexOf (objectToRemove)); | |||
} | |||
/** Removes a range of objects from the array. | |||
This will remove a set of objects, starting from the given index, | |||
and move any subsequent elements down to close the gap. | |||
If the range extends beyond the bounds of the array, it will | |||
be safely clipped to the size of the array. | |||
The objects that are removed will have their reference counts decreased, | |||
and may be deleted if not referenced from elsewhere. | |||
@param startIndex the index of the first object to remove | |||
@param numberToRemove how many objects should be removed | |||
@see remove, removeObject | |||
*/ | |||
void removeRange (const int startIndex, | |||
const int numberToRemove) | |||
{ | |||
const int start = jlimit (0, numUsed, startIndex); | |||
const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); | |||
if (endIndex > start) | |||
{ | |||
int i; | |||
for (i = start; i < endIndex; ++i) | |||
{ | |||
if (ObjectClass* o = data.elements[i]) | |||
{ | |||
releaseObject (o); | |||
data.elements[i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) | |||
} | |||
} | |||
const int rangeSize = endIndex - start; | |||
ObjectClass** e = data.elements + start; | |||
i = numUsed - endIndex; | |||
numUsed -= rangeSize; | |||
while (--i >= 0) | |||
{ | |||
*e = e [rangeSize]; | |||
++e; | |||
} | |||
if ((numUsed << 1) < data.numAllocated) | |||
minimiseStorageOverheads(); | |||
} | |||
} | |||
/** Removes the last n objects from the array. | |||
The objects that are removed will have their reference counts decreased, | |||
and may be deleted if not referenced from elsewhere. | |||
@param howManyToRemove how many objects to remove from the end of the array | |||
@see remove, removeObject, removeRange | |||
*/ | |||
void removeLast (int howManyToRemove = 1) | |||
{ | |||
if (howManyToRemove > numUsed) | |||
howManyToRemove = numUsed; | |||
while (--howManyToRemove >= 0) | |||
remove (numUsed - 1); | |||
} | |||
/** Swaps a pair of objects in the array. | |||
If either of the indexes passed in is out-of-range, nothing will happen, | |||
otherwise the two objects at these positions will be exchanged. | |||
*/ | |||
void swap (const int index1, | |||
const int index2) noexcept | |||
{ | |||
if (isPositiveAndBelow (index1, numUsed) | |||
&& isPositiveAndBelow (index2, numUsed)) | |||
{ | |||
std::swap (data.elements [index1], | |||
data.elements [index2]); | |||
} | |||
} | |||
/** Moves one of the objects to a different position. | |||
This will move the object to a specified index, shuffling along | |||
any intervening elements as required. | |||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling | |||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||
@param currentIndex the index of the object to be moved. If this isn't a | |||
valid index, then nothing will be done | |||
@param newIndex the index at which you'd like this object to end up. If this | |||
is less than zero, it will be moved to the end of the array | |||
*/ | |||
void move (const int currentIndex, | |||
int newIndex) noexcept | |||
{ | |||
if (currentIndex != newIndex) | |||
{ | |||
if (isPositiveAndBelow (currentIndex, numUsed)) | |||
{ | |||
if (! isPositiveAndBelow (newIndex, numUsed)) | |||
newIndex = numUsed - 1; | |||
ObjectClass* const value = data.elements [currentIndex]; | |||
if (newIndex > currentIndex) | |||
{ | |||
memmove (data.elements + currentIndex, | |||
data.elements + currentIndex + 1, | |||
sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); | |||
} | |||
else | |||
{ | |||
memmove (data.elements + newIndex + 1, | |||
data.elements + newIndex, | |||
sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); | |||
} | |||
data.elements [newIndex] = value; | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
/** This swaps the contents of this array with those of another array. | |||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||
because it just swaps their internal pointers. | |||
*/ | |||
template <class OtherArrayType> | |||
void swapWith (OtherArrayType& otherArray) noexcept | |||
{ | |||
data.swapWith (otherArray.data); | |||
std::swap (numUsed, otherArray.numUsed); | |||
} | |||
//============================================================================== | |||
/** Compares this array to another one. | |||
@returns true only if the other array contains the same objects in the same order | |||
*/ | |||
bool operator== (const ReferenceCountedArray& other) const noexcept | |||
{ | |||
if (numUsed != other.numUsed) | |||
return false; | |||
for (int i = numUsed; --i >= 0;) | |||
if (data.elements [i] != other.data.elements [i]) | |||
return false; | |||
return true; | |||
} | |||
/** Compares this array to another one. | |||
@see operator== | |||
*/ | |||
bool operator!= (const ReferenceCountedArray<ObjectClass>& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
//============================================================================== | |||
/** Sorts the elements in the array. | |||
This will use a comparator object to sort the elements into order. The object | |||
passed must have a method of the form: | |||
@code | |||
int compareElements (ElementType first, ElementType second); | |||
@endcode | |||
..and this method must return: | |||
- a value of < 0 if the first comes before the second | |||
- a value of 0 if the two objects are equivalent | |||
- a value of > 0 if the second comes before the first | |||
To improve performance, the compareElements() method can be declared as static or const. | |||
@param comparator the comparator to use for comparing elements. | |||
@param retainOrderOfEquivalentItems if this is true, then items | |||
which the comparator says are equivalent will be | |||
kept in the order in which they currently appear | |||
in the array. This is slower to perform, but may | |||
be important in some cases. If it's false, a faster | |||
algorithm is used, but equivalent elements may be | |||
rearranged. | |||
@see sortArray | |||
*/ | |||
template <class ElementComparator> | |||
void sort (ElementComparator& comparator, | |||
const bool retainOrderOfEquivalentItems = false) const noexcept | |||
{ | |||
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this | |||
// avoids getting warning messages about the parameter being unused | |||
sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); | |||
} | |||
//============================================================================== | |||
/** Reduces the amount of storage being used by the array. | |||
Arrays typically allocate slightly more storage than they need, and after | |||
removing elements, they may have quite a lot of unused space allocated. | |||
This method will reduce the amount of allocated storage to a minimum. | |||
*/ | |||
void minimiseStorageOverheads() noexcept | |||
{ | |||
data.shrinkToNoMoreThan (numUsed); | |||
} | |||
/** Increases the array's internal storage to hold a minimum number of elements. | |||
Calling this before adding a large known number of elements means that | |||
the array won't have to keep dynamically resizing itself as the elements | |||
are added, and it'll therefore be more efficient. | |||
*/ | |||
void ensureStorageAllocated (const int minNumElements) | |||
{ | |||
data.ensureAllocatedSize (minNumElements); | |||
} | |||
private: | |||
//============================================================================== | |||
ArrayAllocationBase <ObjectClass*> data; | |||
int numUsed; | |||
void releaseAllObjects() | |||
{ | |||
while (numUsed > 0) | |||
if (ObjectClass* o = data.elements [--numUsed]) | |||
releaseObject (o); | |||
jassert (numUsed == 0); | |||
} | |||
static void releaseObject (ObjectClass* o) | |||
{ | |||
if (o->decReferenceCountWithoutDeleting()) | |||
delete o; | |||
} | |||
}; | |||
#endif // JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED |
@@ -0,0 +1,469 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_SORTEDSET_H_INCLUDED | |||
#define JUCE_SORTEDSET_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Holds a set of unique primitive objects, such as ints or doubles. | |||
A set can only hold one item with a given value, so if for example it's a | |||
set of integers, attempting to add the same integer twice will do nothing | |||
the second time. | |||
Internally, the list of items is kept sorted (which means that whatever | |||
kind of primitive type is used must support the ==, <, >, <= and >= operators | |||
to determine the order), and searching the set for known values is very fast | |||
because it uses a binary-chop method. | |||
Note that if you're using a class or struct as the element type, it must be | |||
capable of being copied or moved with a straightforward memcpy, rather than | |||
needing construction and destruction code. | |||
To make all the set's methods thread-safe, pass in "CriticalSection" as the templated | |||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||
@see Array, OwnedArray, ReferenceCountedArray, StringArray, CriticalSection | |||
*/ | |||
template <class ElementType> | |||
class SortedSet | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an empty set. */ | |||
SortedSet() noexcept | |||
{ | |||
} | |||
/** Creates a copy of another set. | |||
@param other the set to copy | |||
*/ | |||
SortedSet (const SortedSet& other) | |||
: data (other.data) | |||
{ | |||
} | |||
/** Destructor. */ | |||
~SortedSet() noexcept | |||
{ | |||
} | |||
/** Copies another set over this one. | |||
@param other the set to copy | |||
*/ | |||
SortedSet& operator= (const SortedSet& other) noexcept | |||
{ | |||
data = other.data; | |||
return *this; | |||
} | |||
//============================================================================== | |||
/** Compares this set to another one. | |||
Two sets are considered equal if they both contain the same set of elements. | |||
@param other the other set to compare with | |||
*/ | |||
bool operator== (const SortedSet<ElementType>& other) const noexcept | |||
{ | |||
return data == other.data; | |||
} | |||
/** Compares this set to another one. | |||
Two sets are considered equal if they both contain the same set of elements. | |||
@param other the other set to compare with | |||
*/ | |||
bool operator!= (const SortedSet<ElementType>& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
//============================================================================== | |||
/** Removes all elements from the set. | |||
This will remove all the elements, and free any storage that the set is | |||
using. To clear it without freeing the storage, use the clearQuick() | |||
method instead. | |||
@see clearQuick | |||
*/ | |||
void clear() noexcept | |||
{ | |||
data.clear(); | |||
} | |||
/** Removes all elements from the set without freeing the array's allocated storage. | |||
@see clear | |||
*/ | |||
void clearQuick() noexcept | |||
{ | |||
data.clearQuick(); | |||
} | |||
//============================================================================== | |||
/** Returns the current number of elements in the set. */ | |||
inline int size() const noexcept | |||
{ | |||
return data.size(); | |||
} | |||
/** Returns true if the set is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
/** Returns one of the elements in the set. | |||
If the index passed in is beyond the range of valid elements, this | |||
will return zero. | |||
If you're certain that the index will always be a valid element, you | |||
can call getUnchecked() instead, which is faster. | |||
@param index the index of the element being requested (0 is the first element in the set) | |||
@see getUnchecked, getFirst, getLast | |||
*/ | |||
inline ElementType operator[] (const int index) const noexcept | |||
{ | |||
return data [index]; | |||
} | |||
/** Returns one of the elements in the set, without checking the index passed in. | |||
Unlike the operator[] method, this will try to return an element without | |||
checking that the index is within the bounds of the set, so should only | |||
be used when you're confident that it will always be a valid index. | |||
@param index the index of the element being requested (0 is the first element in the set) | |||
@see operator[], getFirst, getLast | |||
*/ | |||
inline ElementType getUnchecked (const int index) const noexcept | |||
{ | |||
return data.getUnchecked (index); | |||
} | |||
/** Returns a direct reference to one of the elements in the set, without checking the index passed in. | |||
This is like getUnchecked, but returns a direct reference to the element, so that | |||
you can alter it directly. Obviously this can be dangerous, so only use it when | |||
absolutely necessary. | |||
@param index the index of the element being requested (0 is the first element in the array) | |||
*/ | |||
inline ElementType& getReference (const int index) const noexcept | |||
{ | |||
return data.getReference (index); | |||
} | |||
/** Returns the first element in the set, or 0 if the set is empty. | |||
@see operator[], getUnchecked, getLast | |||
*/ | |||
inline ElementType getFirst() const noexcept | |||
{ | |||
return data.getFirst(); | |||
} | |||
/** Returns the last element in the set, or 0 if the set is empty. | |||
@see operator[], getUnchecked, getFirst | |||
*/ | |||
inline ElementType getLast() const noexcept | |||
{ | |||
return data.getLast(); | |||
} | |||
//============================================================================== | |||
/** Returns a pointer to the first element in the set. | |||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||
*/ | |||
inline ElementType* begin() const noexcept | |||
{ | |||
return data.begin(); | |||
} | |||
/** Returns a pointer to the element which follows the last element in the set. | |||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||
*/ | |||
inline ElementType* end() const noexcept | |||
{ | |||
return data.end(); | |||
} | |||
//============================================================================== | |||
/** Finds the index of the first element which matches the value passed in. | |||
This will search the set for the given object, and return the index | |||
of its first occurrence. If the object isn't found, the method will return -1. | |||
@param elementToLookFor the value or object to look for | |||
@returns the index of the object, or -1 if it's not found | |||
*/ | |||
int indexOf (const ElementType& elementToLookFor) const noexcept | |||
{ | |||
int s = 0; | |||
int e = data.size(); | |||
for (;;) | |||
{ | |||
if (s >= e) | |||
return -1; | |||
if (elementToLookFor == data.getReference (s)) | |||
return s; | |||
const int halfway = (s + e) / 2; | |||
if (halfway == s) | |||
return -1; | |||
if (elementToLookFor < data.getReference (halfway)) | |||
e = halfway; | |||
else | |||
s = halfway; | |||
} | |||
} | |||
/** Returns true if the set contains at least one occurrence of an object. | |||
@param elementToLookFor the value or object to look for | |||
@returns true if the item is found | |||
*/ | |||
bool contains (const ElementType& elementToLookFor) const noexcept | |||
{ | |||
return indexOf (elementToLookFor) >= 0; | |||
} | |||
//============================================================================== | |||
/** Adds a new element to the set, (as long as it's not already in there). | |||
Note that if a matching element already exists, the new value will be assigned | |||
to the existing one using operator=, so that if there are any differences between | |||
the objects which were not recognised by the object's operator==, then the | |||
set will always contain a copy of the most recently added one. | |||
@param newElement the new object to add to the set | |||
@returns true if the value was added, or false if it already existed | |||
@see set, insert, addIfNotAlreadyThere, addSorted, addSet, addArray | |||
*/ | |||
bool add (const ElementType& newElement) noexcept | |||
{ | |||
int s = 0; | |||
int e = data.size(); | |||
while (s < e) | |||
{ | |||
ElementType& elem = data.getReference (s); | |||
if (newElement == elem) | |||
{ | |||
elem = newElement; // force an update in case operator== permits differences. | |||
return false; | |||
} | |||
const int halfway = (s + e) / 2; | |||
const bool isBeforeHalfway = (newElement < data.getReference (halfway)); | |||
if (halfway == s) | |||
{ | |||
if (! isBeforeHalfway) | |||
++s; | |||
break; | |||
} | |||
if (isBeforeHalfway) | |||
e = halfway; | |||
else | |||
s = halfway; | |||
} | |||
data.insert (s, newElement); | |||
return true; | |||
} | |||
/** Adds elements from an array to this set. | |||
@param elementsToAdd the array of elements to add | |||
@param numElementsToAdd how many elements are in this other array | |||
@see add | |||
*/ | |||
void addArray (const ElementType* elementsToAdd, | |||
int numElementsToAdd) noexcept | |||
{ | |||
while (--numElementsToAdd >= 0) | |||
add (*elementsToAdd++); | |||
} | |||
/** Adds elements from another set to this one. | |||
@param setToAddFrom the set from which to copy the elements | |||
@param startIndex the first element of the other set to start copying from | |||
@param numElementsToAdd how many elements to add from the other set. If this | |||
value is negative or greater than the number of available elements, | |||
all available elements will be copied. | |||
@see add | |||
*/ | |||
template <class OtherSetType> | |||
void addSet (const OtherSetType& setToAddFrom, | |||
int startIndex = 0, | |||
int numElementsToAdd = -1) noexcept | |||
{ | |||
jassert (this != &setToAddFrom); | |||
if (this != &setToAddFrom) | |||
{ | |||
if (startIndex < 0) | |||
{ | |||
jassertfalse; | |||
startIndex = 0; | |||
} | |||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) | |||
numElementsToAdd = setToAddFrom.size() - startIndex; | |||
if (numElementsToAdd > 0) | |||
addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); | |||
} | |||
} | |||
//============================================================================== | |||
/** Removes an element from the set. | |||
This will remove the element at a given index. | |||
If the index passed in is out-of-range, nothing will happen. | |||
@param indexToRemove the index of the element to remove | |||
@returns the element that has been removed | |||
@see removeValue, removeRange | |||
*/ | |||
ElementType remove (const int indexToRemove) noexcept | |||
{ | |||
return data.removeAndReturn (indexToRemove); | |||
} | |||
/** Removes an item from the set. | |||
This will remove the given element from the set, if it's there. | |||
@param valueToRemove the object to try to remove | |||
@see remove, removeRange | |||
*/ | |||
void removeValue (const ElementType valueToRemove) noexcept | |||
{ | |||
data.remove (indexOf (valueToRemove)); | |||
} | |||
/** Removes any elements which are also in another set. | |||
@param otherSet the other set in which to look for elements to remove | |||
@see removeValuesNotIn, remove, removeValue, removeRange | |||
*/ | |||
template <class OtherSetType> | |||
void removeValuesIn (const OtherSetType& otherSet) noexcept | |||
{ | |||
if (this == &otherSet) | |||
{ | |||
clear(); | |||
} | |||
else if (otherSet.size() > 0) | |||
{ | |||
for (int i = data.size(); --i >= 0;) | |||
if (otherSet.contains (data.getReference (i))) | |||
remove (i); | |||
} | |||
} | |||
/** Removes any elements which are not found in another set. | |||
Only elements which occur in this other set will be retained. | |||
@param otherSet the set in which to look for elements NOT to remove | |||
@see removeValuesIn, remove, removeValue, removeRange | |||
*/ | |||
template <class OtherSetType> | |||
void removeValuesNotIn (const OtherSetType& otherSet) noexcept | |||
{ | |||
if (this != &otherSet) | |||
{ | |||
if (otherSet.size() <= 0) | |||
{ | |||
clear(); | |||
} | |||
else | |||
{ | |||
for (int i = data.size(); --i >= 0;) | |||
if (! otherSet.contains (data.getReference (i))) | |||
remove (i); | |||
} | |||
} | |||
} | |||
/** This swaps the contents of this array with those of another array. | |||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||
because it just swaps their internal pointers. | |||
*/ | |||
template <class OtherSetType> | |||
void swapWith (OtherSetType& otherSet) noexcept | |||
{ | |||
data.swapWith (otherSet.data); | |||
} | |||
//============================================================================== | |||
/** Reduces the amount of storage being used by the set. | |||
Sets typically allocate slightly more storage than they need, and after | |||
removing elements, they may have quite a lot of unused space allocated. | |||
This method will reduce the amount of allocated storage to a minimum. | |||
*/ | |||
void minimiseStorageOverheads() noexcept | |||
{ | |||
data.minimiseStorageOverheads(); | |||
} | |||
/** Increases the set's internal storage to hold a minimum number of elements. | |||
Calling this before adding a large known number of elements means that | |||
the set won't have to keep dynamically resizing itself as the elements | |||
are added, and it'll therefore be more efficient. | |||
*/ | |||
void ensureStorageAllocated (const int minNumElements) | |||
{ | |||
data.ensureStorageAllocated (minNumElements); | |||
} | |||
private: | |||
//============================================================================== | |||
Array<ElementType> data; | |||
}; | |||
#if JUCE_MSVC | |||
#pragma warning (pop) | |||
#endif | |||
#endif // JUCE_SORTEDSET_H_INCLUDED |
@@ -0,0 +1,987 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_FILE_H_INCLUDED | |||
#define JUCE_FILE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Represents a local file or directory. | |||
This class encapsulates the absolute pathname of a file or directory, and | |||
has methods for finding out about the file and changing its properties. | |||
To read or write to the file, there are methods for returning an input or | |||
output stream. | |||
@see FileInputStream, FileOutputStream | |||
*/ | |||
class JUCE_API File | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an (invalid) file object. | |||
The file is initially set to an empty path, so getFullPathName() will return | |||
an empty string. | |||
You can use its operator= method to point it at a proper file. | |||
*/ | |||
File() noexcept {} | |||
/** Creates a file from an absolute path. | |||
If the path supplied is a relative path, it is taken to be relative | |||
to the current working directory (see File::getCurrentWorkingDirectory()), | |||
but this isn't a recommended way of creating a file, because you | |||
never know what the CWD is going to be. | |||
On the Mac/Linux, the path can include "~" notation for referring to | |||
user home directories. | |||
*/ | |||
File (const String& absolutePath); | |||
/** Creates a copy of another file object. */ | |||
File (const File&); | |||
/** Destructor. */ | |||
~File() noexcept {} | |||
/** Sets the file based on an absolute pathname. | |||
If the path supplied is a relative path, it is taken to be relative | |||
to the current working directory (see File::getCurrentWorkingDirectory()), | |||
but this isn't a recommended way of creating a file, because you | |||
never know what the CWD is going to be. | |||
On the Mac/Linux, the path can include "~" notation for referring to | |||
user home directories. | |||
*/ | |||
File& operator= (const String& newAbsolutePath); | |||
/** Copies from another file object. */ | |||
File& operator= (const File& otherFile); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
File (File&&) noexcept; | |||
File& operator= (File&&) noexcept; | |||
#endif | |||
//============================================================================== | |||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
/** This static constant is used for referring to an 'invalid' file. | |||
Bear in mind that you should avoid this kind of static variable, and always prefer | |||
to use File() or {} if you need a default-constructed File object. | |||
*/ | |||
static const File nonexistent; | |||
#endif | |||
//============================================================================== | |||
/** Checks whether the file actually exists. | |||
@returns true if the file exists, either as a file or a directory. | |||
@see existsAsFile, isDirectory | |||
*/ | |||
bool exists() const; | |||
/** Checks whether the file exists and is a file rather than a directory. | |||
@returns true only if this is a real file, false if it's a directory | |||
or doesn't exist | |||
@see exists, isDirectory | |||
*/ | |||
bool existsAsFile() const; | |||
/** Checks whether the file is a directory that exists. | |||
@returns true only if the file is a directory which actually exists, so | |||
false if it's a file or doesn't exist at all | |||
@see exists, existsAsFile | |||
*/ | |||
bool isDirectory() const; | |||
/** Returns the size of the file in bytes. | |||
@returns the number of bytes in the file, or 0 if it doesn't exist. | |||
*/ | |||
int64 getSize() const; | |||
/** Utility function to convert a file size in bytes to a neat string description. | |||
So for example 100 would return "100 bytes", 2000 would return "2 KB", | |||
2000000 would produce "2 MB", etc. | |||
*/ | |||
static String descriptionOfSizeInBytes (int64 bytes); | |||
//============================================================================== | |||
/** Returns the complete, absolute path of this file. | |||
This includes the filename and all its parent folders. On Windows it'll | |||
also include the drive letter prefix; on Mac or Linux it'll be a complete | |||
path starting from the root folder. | |||
If you just want the file's name, you should use getFileName() or | |||
getFileNameWithoutExtension(). | |||
@see getFileName, getRelativePathFrom | |||
*/ | |||
const String& getFullPathName() const noexcept { return fullPath; } | |||
/** Returns the last section of the pathname. | |||
Returns just the final part of the path - e.g. if the whole path | |||
is "/moose/fish/foo.txt" this will return "foo.txt". | |||
For a directory, it returns the final part of the path - e.g. for the | |||
directory "/moose/fish" it'll return "fish". | |||
If the filename begins with a dot, it'll return the whole filename, e.g. for | |||
"/moose/.fish", it'll return ".fish" | |||
@see getFullPathName, getFileNameWithoutExtension | |||
*/ | |||
String getFileName() const; | |||
/** Creates a relative path that refers to a file relatively to a given directory. | |||
e.g. File ("/moose/foo.txt").getRelativePathFrom (File ("/moose/fish/haddock")) | |||
would return "../../foo.txt". | |||
If it's not possible to navigate from one file to the other, an absolute | |||
path is returned. If the paths are invalid, an empty string may also be | |||
returned. | |||
@param directoryToBeRelativeTo the directory which the resultant string will | |||
be relative to. If this is actually a file rather than | |||
a directory, its parent directory will be used instead. | |||
If it doesn't exist, it's assumed to be a directory. | |||
@see getChildFile, isAbsolutePath | |||
*/ | |||
String getRelativePathFrom (const File& directoryToBeRelativeTo) const; | |||
//============================================================================== | |||
/** Returns the file's extension. | |||
Returns the file extension of this file, also including the dot. | |||
e.g. "/moose/fish/foo.txt" would return ".txt" | |||
@see hasFileExtension, withFileExtension, getFileNameWithoutExtension | |||
*/ | |||
String getFileExtension() const; | |||
/** Checks whether the file has a given extension. | |||
@param extensionToTest the extension to look for - it doesn't matter whether or | |||
not this string has a dot at the start, so ".wav" and "wav" | |||
will have the same effect. To compare with multiple extensions, this | |||
parameter can contain multiple strings, separated by semi-colons - | |||
so, for example: hasFileExtension (".jpeg;png;gif") would return | |||
true if the file has any of those three extensions. | |||
@see getFileExtension, withFileExtension, getFileNameWithoutExtension | |||
*/ | |||
bool hasFileExtension (StringRef extensionToTest) const; | |||
/** Returns a version of this file with a different file extension. | |||
e.g. File ("/moose/fish/foo.txt").withFileExtension ("html") returns "/moose/fish/foo.html" | |||
@param newExtension the new extension, either with or without a dot at the start (this | |||
doesn't make any difference). To get remove a file's extension altogether, | |||
pass an empty string into this function. | |||
@see getFileName, getFileExtension, hasFileExtension, getFileNameWithoutExtension | |||
*/ | |||
File withFileExtension (StringRef newExtension) const; | |||
/** Returns the last part of the filename, without its file extension. | |||
e.g. for "/moose/fish/foo.txt" this will return "foo". | |||
@see getFileName, getFileExtension, hasFileExtension, withFileExtension | |||
*/ | |||
String getFileNameWithoutExtension() const; | |||
//============================================================================== | |||
/** Returns a 32-bit hash-code that identifies this file. | |||
This is based on the filename. Obviously it's possible, although unlikely, that | |||
two files will have the same hash-code. | |||
*/ | |||
int hashCode() const; | |||
/** Returns a 64-bit hash-code that identifies this file. | |||
This is based on the filename. Obviously it's possible, although unlikely, that | |||
two files will have the same hash-code. | |||
*/ | |||
int64 hashCode64() const; | |||
//============================================================================== | |||
/** Returns a file that represents a relative (or absolute) sub-path of the current one. | |||
This will find a child file or directory of the current object. | |||
e.g. | |||
File ("/moose/fish").getChildFile ("foo.txt") will produce "/moose/fish/foo.txt". | |||
File ("/moose/fish").getChildFile ("haddock/foo.txt") will produce "/moose/fish/haddock/foo.txt". | |||
File ("/moose/fish").getChildFile ("../foo.txt") will produce "/moose/foo.txt". | |||
If the string is actually an absolute path, it will be treated as such, e.g. | |||
File ("/moose/fish").getChildFile ("/foo.txt") will produce "/foo.txt" | |||
@see getSiblingFile, getParentDirectory, getRelativePathFrom, isAChildOf | |||
*/ | |||
File getChildFile (StringRef relativeOrAbsolutePath) const; | |||
/** Returns a file which is in the same directory as this one. | |||
This is equivalent to getParentDirectory().getChildFile (name). | |||
@see getChildFile, getParentDirectory | |||
*/ | |||
File getSiblingFile (StringRef siblingFileName) const; | |||
//============================================================================== | |||
/** Returns the directory that contains this file or directory. | |||
e.g. for "/moose/fish/foo.txt" this will return "/moose/fish". | |||
*/ | |||
File getParentDirectory() const; | |||
/** Checks whether a file is somewhere inside a directory. | |||
Returns true if this file is somewhere inside a subdirectory of the directory | |||
that is passed in. Neither file actually has to exist, because the function | |||
just checks the paths for similarities. | |||
e.g. File ("/moose/fish/foo.txt").isAChildOf ("/moose") is true. | |||
File ("/moose/fish/foo.txt").isAChildOf ("/moose/fish") is also true. | |||
*/ | |||
bool isAChildOf (const File& potentialParentDirectory) const; | |||
//============================================================================== | |||
/** Chooses a filename relative to this one that doesn't already exist. | |||
If this file is a directory, this will return a child file of this | |||
directory that doesn't exist, by adding numbers to a prefix and suffix until | |||
it finds one that isn't already there. | |||
If the prefix + the suffix doesn't exist, it won't bother adding a number. | |||
e.g. File ("/moose/fish").getNonexistentChildFile ("foo", ".txt", true) might | |||
return "/moose/fish/foo(2).txt" if there's already a file called "foo.txt". | |||
@param prefix the string to use for the filename before the number | |||
@param suffix the string to add to the filename after the number | |||
@param putNumbersInBrackets if true, this will create filenames in the | |||
format "prefix(number)suffix", if false, it will leave the | |||
brackets out. | |||
*/ | |||
File getNonexistentChildFile (const String& prefix, | |||
const String& suffix, | |||
bool putNumbersInBrackets = true) const; | |||
/** Chooses a filename for a sibling file to this one that doesn't already exist. | |||
If this file doesn't exist, this will just return itself, otherwise it | |||
will return an appropriate sibling that doesn't exist, e.g. if a file | |||
"/moose/fish/foo.txt" exists, this might return "/moose/fish/foo(2).txt". | |||
@param putNumbersInBrackets whether to add brackets around the numbers that | |||
get appended to the new filename. | |||
*/ | |||
File getNonexistentSibling (bool putNumbersInBrackets = true) const; | |||
//============================================================================== | |||
/** Compares the pathnames for two files. */ | |||
bool operator== (const File&) const; | |||
/** Compares the pathnames for two files. */ | |||
bool operator!= (const File&) const; | |||
/** Compares the pathnames for two files. */ | |||
bool operator< (const File&) const; | |||
/** Compares the pathnames for two files. */ | |||
bool operator> (const File&) const; | |||
//============================================================================== | |||
/** Checks whether a file can be created or written to. | |||
@returns true if it's possible to create and write to this file. If the file | |||
doesn't already exist, this will check its parent directory to | |||
see if writing is allowed. | |||
@see setReadOnly | |||
*/ | |||
bool hasWriteAccess() const; | |||
/** Changes the write-permission of a file or directory. | |||
@param shouldBeReadOnly whether to add or remove write-permission | |||
@param applyRecursively if the file is a directory and this is true, it will | |||
recurse through all the subfolders changing the permissions | |||
of all files | |||
@returns true if it manages to change the file's permissions. | |||
@see hasWriteAccess | |||
*/ | |||
bool setReadOnly (bool shouldBeReadOnly, | |||
bool applyRecursively = false) const; | |||
/** Changes the execute-permissions of a file. | |||
@param shouldBeExecutable whether to add or remove execute-permission | |||
@returns true if it manages to change the file's permissions. | |||
*/ | |||
bool setExecutePermission (bool shouldBeExecutable) const; | |||
/** Returns true if this file is a hidden or system file. | |||
The criteria for deciding whether a file is hidden are platform-dependent. | |||
*/ | |||
bool isHidden() const; | |||
/** Returns a unique identifier for the file, if one is available. | |||
Depending on the OS and file-system, this may be a unix inode number or | |||
a win32 file identifier, or 0 if it fails to find one. The number will | |||
be unique on the filesystem, but not globally. | |||
*/ | |||
uint64 getFileIdentifier() const; | |||
/** If possible, this will try to create a version string for the given file. | |||
The OS may be able to look at the file and give a version for it - e.g. with | |||
executables, bundles, dlls, etc. If no version is available, this will | |||
return an empty string. | |||
*/ | |||
String getVersion() const; | |||
//============================================================================== | |||
/** Creates an empty file if it doesn't already exist. | |||
If the file that this object refers to doesn't exist, this will create a file | |||
of zero size. | |||
If it already exists or is a directory, this method will do nothing. | |||
If the parent directories of the File do not exist then this method will | |||
recursively create the parent directories. | |||
@returns a result to indicate whether the file was created successfully, | |||
or an error message if it failed. | |||
@see createDirectory | |||
*/ | |||
Result create() const; | |||
/** Creates a new directory for this filename. | |||
This will try to create the file as a directory, and will also create | |||
any parent directories it needs in order to complete the operation. | |||
@returns a result to indicate whether the directory was created successfully, or | |||
an error message if it failed. | |||
@see create | |||
*/ | |||
Result createDirectory() const; | |||
/** Deletes a file. | |||
If this file is actually a directory, it may not be deleted correctly if it | |||
contains files. See deleteRecursively() as a better way of deleting directories. | |||
@returns true if the file has been successfully deleted (or if it didn't exist to | |||
begin with). | |||
@see deleteRecursively | |||
*/ | |||
bool deleteFile() const; | |||
/** Deletes a file or directory and all its subdirectories. | |||
If this file is a directory, this will try to delete it and all its subfolders. If | |||
it's just a file, it will just try to delete the file. | |||
@returns true if the file and all its subfolders have been successfully deleted | |||
(or if it didn't exist to begin with). | |||
@see deleteFile | |||
*/ | |||
bool deleteRecursively() const; | |||
/** Moves this file or folder to the trash. | |||
@returns true if the operation succeeded. It could fail if the trash is full, or | |||
if the file is write-protected, so you should check the return value | |||
and act appropriately. | |||
*/ | |||
bool moveToTrash() const; | |||
/** Moves or renames a file. | |||
Tries to move a file to a different location. | |||
If the target file already exists, this will attempt to delete it first, and | |||
will fail if this can't be done. | |||
Note that the destination file isn't the directory to put it in, it's the actual | |||
filename that you want the new file to have. | |||
Also note that on some OSes (e.g. Windows), moving files between different | |||
volumes may not be possible. | |||
@returns true if the operation succeeds | |||
*/ | |||
bool moveFileTo (const File& targetLocation) const; | |||
/** Copies a file. | |||
Tries to copy a file to a different location. | |||
If the target file already exists, this will attempt to delete it first, and | |||
will fail if this can't be done. | |||
@returns true if the operation succeeds | |||
*/ | |||
bool copyFileTo (const File& targetLocation) const; | |||
/** Replaces a file. | |||
Replace the file in the given location, assuming the replaced files identity. | |||
Depending on the file system this will preserve file attributes such as | |||
creation date, short file name, etc. | |||
If replacement succeeds the original file is deleted. | |||
@returns true if the operation succeeds | |||
*/ | |||
bool replaceFileIn (const File& targetLocation) const; | |||
/** Copies a directory. | |||
Tries to copy an entire directory, recursively. | |||
If this file isn't a directory or if any target files can't be created, this | |||
will return false. | |||
@param newDirectory the directory that this one should be copied to. Note that this | |||
is the name of the actual directory to create, not the directory | |||
into which the new one should be placed, so there must be enough | |||
write privileges to create it if it doesn't exist. Any files inside | |||
it will be overwritten by similarly named ones that are copied. | |||
*/ | |||
bool copyDirectoryTo (const File& newDirectory) const; | |||
//============================================================================== | |||
/** Used in file searching, to specify whether to return files, directories, or both. | |||
*/ | |||
enum TypesOfFileToFind | |||
{ | |||
findDirectories = 1, /**< Use this flag to indicate that you want to find directories. */ | |||
findFiles = 2, /**< Use this flag to indicate that you want to find files. */ | |||
findFilesAndDirectories = 3, /**< Use this flag to indicate that you want to find both files and directories. */ | |||
ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */ | |||
}; | |||
/** Searches inside a directory for files matching a wildcard pattern. | |||
Assuming that this file is a directory, this method will search it | |||
for either files or subdirectories whose names match a filename pattern. | |||
@param results an array to which File objects will be added for the | |||
files that the search comes up with | |||
@param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to | |||
return files, directories, or both. If the ignoreHiddenFiles flag | |||
is also added to this value, hidden files won't be returned | |||
@param searchRecursively if true, all subdirectories will be recursed into to do | |||
an exhaustive search | |||
@param wildCardPattern the filename pattern to search for, e.g. "*.txt" | |||
@returns the number of results that have been found | |||
@see getNumberOfChildFiles, DirectoryIterator | |||
*/ | |||
int findChildFiles (Array<File>& results, | |||
int whatToLookFor, | |||
bool searchRecursively, | |||
const String& wildCardPattern = "*") const; | |||
/** Searches inside a directory and counts how many files match a wildcard pattern. | |||
Assuming that this file is a directory, this method will search it | |||
for either files or subdirectories whose names match a filename pattern, | |||
and will return the number of matches found. | |||
This isn't a recursive call, and will only search this directory, not | |||
its children. | |||
@param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to | |||
count files, directories, or both. If the ignoreHiddenFiles flag | |||
is also added to this value, hidden files won't be counted | |||
@param wildCardPattern the filename pattern to search for, e.g. "*.txt" | |||
@returns the number of matches found | |||
@see findChildFiles, DirectoryIterator | |||
*/ | |||
int getNumberOfChildFiles (int whatToLookFor, | |||
const String& wildCardPattern = "*") const; | |||
/** Returns true if this file is a directory that contains one or more subdirectories. | |||
@see isDirectory, findChildFiles | |||
*/ | |||
bool containsSubDirectories() const; | |||
//============================================================================== | |||
/** Creates a stream to read from this file. | |||
@returns a stream that will read from this file (initially positioned at the | |||
start of the file), or nullptr if the file can't be opened for some reason | |||
@see createOutputStream, loadFileAsData | |||
*/ | |||
FileInputStream* createInputStream() const; | |||
/** Creates a stream to write to this file. | |||
If the file exists, the stream that is returned will be positioned ready for | |||
writing at the end of the file, so you might want to use deleteFile() first | |||
to write to an empty file. | |||
@returns a stream that will write to this file (initially positioned at the | |||
end of the file), or nullptr if the file can't be opened for some reason | |||
@see createInputStream, appendData, appendText | |||
*/ | |||
FileOutputStream* createOutputStream (size_t bufferSize = 0x8000) const; | |||
//============================================================================== | |||
/** Loads a file's contents into memory as a block of binary data. | |||
Of course, trying to load a very large file into memory will blow up, so | |||
it's better to check first. | |||
@param result the data block to which the file's contents should be appended - note | |||
that if the memory block might already contain some data, you | |||
might want to clear it first | |||
@returns true if the file could all be read into memory | |||
*/ | |||
bool loadFileAsData (MemoryBlock& result) const; | |||
/** Reads a file into memory as a string. | |||
Attempts to load the entire file as a zero-terminated string. | |||
This makes use of InputStream::readEntireStreamAsString, which can | |||
read either UTF-16 or UTF-8 file formats. | |||
*/ | |||
String loadFileAsString() const; | |||
/** Reads the contents of this file as text and splits it into lines, which are | |||
appended to the given StringArray. | |||
*/ | |||
void readLines (StringArray& destLines) const; | |||
//============================================================================== | |||
/** Appends a block of binary data to the end of the file. | |||
This will try to write the given buffer to the end of the file. | |||
@returns false if it can't write to the file for some reason | |||
*/ | |||
bool appendData (const void* dataToAppend, | |||
size_t numberOfBytes) const; | |||
/** Replaces this file's contents with a given block of data. | |||
This will delete the file and replace it with the given data. | |||
A nice feature of this method is that it's safe - instead of deleting | |||
the file first and then re-writing it, it creates a new temporary file, | |||
writes the data to that, and then moves the new file to replace the existing | |||
file. This means that if the power gets pulled out or something crashes, | |||
you're a lot less likely to end up with a corrupted or unfinished file.. | |||
Returns true if the operation succeeds, or false if it fails. | |||
@see appendText | |||
*/ | |||
bool replaceWithData (const void* dataToWrite, | |||
size_t numberOfBytes) const; | |||
/** Appends a string to the end of the file. | |||
This will try to append a text string to the file, as either 16-bit unicode | |||
or 8-bit characters in the default system encoding. | |||
It can also write the 'ff fe' unicode header bytes before the text to indicate | |||
the endianness of the file. | |||
Any single \\n characters in the string are replaced with \\r\\n before it is written. | |||
@see replaceWithText | |||
*/ | |||
bool appendText (const String& textToAppend, | |||
bool asUnicode = false, | |||
bool writeUnicodeHeaderBytes = false) const; | |||
/** Replaces this file's contents with a given text string. | |||
This will delete the file and replace it with the given text. | |||
A nice feature of this method is that it's safe - instead of deleting | |||
the file first and then re-writing it, it creates a new temporary file, | |||
writes the text to that, and then moves the new file to replace the existing | |||
file. This means that if the power gets pulled out or something crashes, | |||
you're a lot less likely to end up with an empty file.. | |||
For an explanation of the parameters here, see the appendText() method. | |||
Returns true if the operation succeeds, or false if it fails. | |||
@see appendText | |||
*/ | |||
bool replaceWithText (const String& textToWrite, | |||
bool asUnicode = false, | |||
bool writeUnicodeHeaderBytes = false) const; | |||
/** Attempts to scan the contents of this file and compare it to another file, returning | |||
true if this is possible and they match byte-for-byte. | |||
*/ | |||
bool hasIdenticalContentTo (const File& other) const; | |||
//============================================================================== | |||
/** Creates a set of files to represent each file root. | |||
e.g. on Windows this will create files for "c:\", "d:\" etc according | |||
to which ones are available. On the Mac/Linux, this will probably | |||
just add a single entry for "/". | |||
*/ | |||
static void findFileSystemRoots (Array<File>& results); | |||
/** Finds the name of the drive on which this file lives. | |||
@returns the volume label of the drive, or an empty string if this isn't possible | |||
*/ | |||
String getVolumeLabel() const; | |||
/** Returns the serial number of the volume on which this file lives. | |||
@returns the serial number, or zero if there's a problem doing this | |||
*/ | |||
int getVolumeSerialNumber() const; | |||
/** Returns the number of bytes free on the drive that this file lives on. | |||
@returns the number of bytes free, or 0 if there's a problem finding this out | |||
@see getVolumeTotalSize | |||
*/ | |||
int64 getBytesFreeOnVolume() const; | |||
/** Returns the total size of the drive that contains this file. | |||
@returns the total number of bytes that the volume can hold | |||
@see getBytesFreeOnVolume | |||
*/ | |||
int64 getVolumeTotalSize() const; | |||
/** Returns true if this file is on a CD or DVD drive. */ | |||
bool isOnCDRomDrive() const; | |||
/** Returns true if this file is on a hard disk. | |||
This will fail if it's a network drive, but will still be true for | |||
removable hard-disks. | |||
*/ | |||
bool isOnHardDisk() const; | |||
/** Returns true if this file is on a removable disk drive. | |||
This might be a usb-drive, a CD-rom, or maybe a network drive. | |||
*/ | |||
bool isOnRemovableDrive() const; | |||
//============================================================================== | |||
/** Launches the file as a process. | |||
- if the file is executable, this will run it. | |||
- if it's a document of some kind, it will launch the document with its | |||
default viewer application. | |||
- if it's a folder, it will be opened in Explorer, Finder, or equivalent. | |||
@see revealToUser | |||
*/ | |||
bool startAsProcess (const String& parameters = String()) const; | |||
/** Opens Finder, Explorer, or whatever the OS uses, to show the user this file's location. | |||
@see startAsProcess | |||
*/ | |||
void revealToUser() const; | |||
//============================================================================== | |||
/** A set of types of location that can be passed to the getSpecialLocation() method. | |||
*/ | |||
enum SpecialLocationType | |||
{ | |||
/** The user's home folder. This is the same as using File ("~"). */ | |||
userHomeDirectory, | |||
/** The user's default documents folder. On Windows, this might be the user's | |||
"My Documents" folder. On the Mac it'll be their "Documents" folder. Linux | |||
doesn't tend to have one of these, so it might just return their home folder. | |||
*/ | |||
userDocumentsDirectory, | |||
/** The folder that contains the user's desktop objects. */ | |||
userDesktopDirectory, | |||
/** The most likely place where a user might store their music files. */ | |||
userMusicDirectory, | |||
/** The most likely place where a user might store their movie files. */ | |||
userMoviesDirectory, | |||
/** The most likely place where a user might store their picture files. */ | |||
userPicturesDirectory, | |||
/** The folder in which applications store their persistent user-specific settings. | |||
On Windows, this might be "\Documents and Settings\username\Application Data". | |||
On the Mac, it might be "~/Library". If you're going to store your settings in here, | |||
always create your own sub-folder to put them in, to avoid making a mess. | |||
*/ | |||
userApplicationDataDirectory, | |||
/** An equivalent of the userApplicationDataDirectory folder that is shared by all users | |||
of the computer, rather than just the current user. | |||
On the Mac it'll be "/Library", on Windows, it could be something like | |||
"\Documents and Settings\All Users\Application Data". | |||
Depending on the setup, this folder may be read-only. | |||
*/ | |||
commonApplicationDataDirectory, | |||
/** A place to put documents which are shared by all users of the machine. | |||
On Windows this may be somewhere like "C:\Users\Public\Documents", on OSX it | |||
will be something like "/Users/Shared". Other OSes may have no such concept | |||
though, so be careful. | |||
*/ | |||
commonDocumentsDirectory, | |||
/** The folder that should be used for temporary files. | |||
Always delete them when you're finished, to keep the user's computer tidy! | |||
*/ | |||
tempDirectory, | |||
/** Returns this application's executable file. | |||
If running as a plug-in or DLL, this will (where possible) be the DLL rather than the | |||
host app. | |||
On the mac this will return the unix binary, not the package folder - see | |||
currentApplicationFile for that. | |||
See also invokedExecutableFile, which is similar, but if the exe was launched from a | |||
file link, invokedExecutableFile will return the name of the link. | |||
*/ | |||
currentExecutableFile, | |||
/** Returns this application's location. | |||
If running as a plug-in or DLL, this will (where possible) be the DLL rather than the | |||
host app. | |||
On the mac this will return the package folder (if it's in one), not the unix binary | |||
that's inside it - compare with currentExecutableFile. | |||
*/ | |||
currentApplicationFile, | |||
/** Returns the file that was invoked to launch this executable. | |||
This may differ from currentExecutableFile if the app was started from e.g. a link - this | |||
will return the name of the link that was used, whereas currentExecutableFile will return | |||
the actual location of the target executable. | |||
*/ | |||
invokedExecutableFile, | |||
/** In a plugin, this will return the path of the host executable. */ | |||
hostApplicationPath, | |||
#if JUCE_WINDOWS | |||
/** On a Windows machine, returns the location of the Windows/System32 folder. */ | |||
windowsSystemDirectory, | |||
#endif | |||
/** The directory in which applications normally get installed. | |||
So on windows, this would be something like "c:\program files", on the | |||
Mac "/Applications", or "/usr" on linux. | |||
*/ | |||
globalApplicationsDirectory | |||
}; | |||
/** Finds the location of a special type of file or directory, such as a home folder or | |||
documents folder. | |||
@see SpecialLocationType | |||
*/ | |||
static File JUCE_CALLTYPE getSpecialLocation (const SpecialLocationType type); | |||
//============================================================================== | |||
/** Returns a temporary file in the system's temp directory. | |||
This will try to return the name of a non-existent temp file. | |||
To get the temp folder, you can use getSpecialLocation (File::tempDirectory). | |||
*/ | |||
static File createTempFile (StringRef fileNameEnding); | |||
//============================================================================== | |||
/** Returns the current working directory. | |||
@see setAsCurrentWorkingDirectory | |||
*/ | |||
static File getCurrentWorkingDirectory(); | |||
/** Sets the current working directory to be this file. | |||
For this to work the file must point to a valid directory. | |||
@returns true if the current directory has been changed. | |||
@see getCurrentWorkingDirectory | |||
*/ | |||
bool setAsCurrentWorkingDirectory() const; | |||
//============================================================================== | |||
/** The system-specific file separator character. | |||
On Windows, this will be '\', on Mac/Linux, it'll be '/' | |||
*/ | |||
static const juce_wchar separator; | |||
/** The system-specific file separator character, as a string. | |||
On Windows, this will be '\', on Mac/Linux, it'll be '/' | |||
*/ | |||
static const String separatorString; | |||
//============================================================================== | |||
/** Returns a version of a filename with any illegal characters removed. | |||
This will return a copy of the given string after removing characters | |||
that are not allowed in a legal filename, and possibly shortening the | |||
string if it's too long. | |||
Because this will remove slashes, don't use it on an absolute pathname - use | |||
createLegalPathName() for that. | |||
@see createLegalPathName | |||
*/ | |||
static String createLegalFileName (const String& fileNameToFix); | |||
/** Returns a version of a path with any illegal characters removed. | |||
Similar to createLegalFileName(), but this won't remove slashes, so can | |||
be used on a complete pathname. | |||
@see createLegalFileName | |||
*/ | |||
static String createLegalPathName (const String& pathNameToFix); | |||
/** Indicates whether filenames are case-sensitive on the current operating system. */ | |||
static bool areFileNamesCaseSensitive(); | |||
/** Returns true if the string seems to be a fully-specified absolute path. */ | |||
static bool isAbsolutePath (StringRef path); | |||
/** Creates a file that simply contains this string, without doing the sanity-checking | |||
that the normal constructors do. | |||
Best to avoid this unless you really know what you're doing. | |||
*/ | |||
static File createFileWithoutCheckingPath (const String& absolutePath) noexcept; | |||
/** Adds a separator character to the end of a path if it doesn't already have one. */ | |||
static String addTrailingSeparator (const String& path); | |||
//============================================================================== | |||
/** Tries to create a symbolic link and returns a boolean to indicate success */ | |||
bool createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const; | |||
/** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ | |||
bool isSymbolicLink() const; | |||
/** If this file is a link or alias, this returns the file that it points to. | |||
If the file isn't actually link, it'll just return itself. | |||
*/ | |||
File getLinkedTarget() const; | |||
#if JUCE_WINDOWS | |||
/** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ | |||
bool createShortcut (const String& description, const File& linkFileToCreate) const; | |||
/** Windows ONLY - Returns true if this is a win32 .LNK file. */ | |||
bool isShortcut() const; | |||
#endif | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS || DOXYGEN | |||
/** OSX ONLY - Finds the OSType of a file from the its resources. */ | |||
OSType getMacOSType() const; | |||
/** OSX ONLY - Returns true if this file is actually a bundle. */ | |||
bool isBundle() const; | |||
#endif | |||
#if JUCE_MAC || DOXYGEN | |||
/** OSX ONLY - Adds this file to the OSX dock */ | |||
void addToDock() const; | |||
#endif | |||
//============================================================================== | |||
struct NaturalFileComparator | |||
{ | |||
NaturalFileComparator (bool shouldPutFoldersFirst) noexcept : foldersFirst (shouldPutFoldersFirst) {} | |||
int compareElements (const File& firstFile, const File& secondFile) const | |||
{ | |||
if (foldersFirst && (firstFile.isDirectory() != secondFile.isDirectory())) | |||
return firstFile.isDirectory() ? -1 : 1; | |||
#if NAMES_ARE_CASE_SENSITIVE | |||
return firstFile.getFullPathName().compareNatural (secondFile.getFullPathName(), true); | |||
#else | |||
return firstFile.getFullPathName().compareNatural (secondFile.getFullPathName(), false); | |||
#endif | |||
} | |||
bool foldersFirst; | |||
}; | |||
private: | |||
//============================================================================== | |||
String fullPath; | |||
static String parseAbsolutePath (const String&); | |||
String getPathUpToLastSlash() const; | |||
Result createDirectoryInternal (const String&) const; | |||
bool copyInternal (const File&) const; | |||
bool moveInternal (const File&) const; | |||
bool replaceInternal (const File&) const; | |||
bool setFileTimesInternal (int64 m, int64 a, int64 c) const; | |||
void getFileTimesInternal (int64& m, int64& a, int64& c) const; | |||
bool setFileReadOnlyInternal (bool) const; | |||
bool setFileExecutableInternal (bool) const; | |||
}; | |||
#endif // JUCE_FILE_H_INCLUDED |
@@ -0,0 +1,119 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
static File createTempFile (const File& parentDirectory, String name, | |||
const String& suffix, const int optionFlags) | |||
{ | |||
if ((optionFlags & TemporaryFile::useHiddenFile) != 0) | |||
name = "." + name; | |||
return parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & TemporaryFile::putNumbersInBrackets) != 0); | |||
} | |||
TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) | |||
: temporaryFile (createTempFile (File::getSpecialLocation (File::tempDirectory), | |||
"temp_" + String::toHexString (Random::getSystemRandom().nextInt()), | |||
suffix, optionFlags)) | |||
{ | |||
} | |||
TemporaryFile::TemporaryFile (const File& target, const int optionFlags) | |||
: temporaryFile (createTempFile (target.getParentDirectory(), | |||
target.getFileNameWithoutExtension() | |||
+ "_temp" + String::toHexString (Random::getSystemRandom().nextInt()), | |||
target.getFileExtension(), optionFlags)), | |||
targetFile (target) | |||
{ | |||
// If you use this constructor, you need to give it a valid target file! | |||
jassert (targetFile != File()); | |||
} | |||
TemporaryFile::TemporaryFile (const File& target, const File& temporary) | |||
: temporaryFile (temporary), targetFile (target) | |||
{ | |||
} | |||
TemporaryFile::~TemporaryFile() | |||
{ | |||
if (! deleteTemporaryFile()) | |||
{ | |||
/* Failed to delete our temporary file! The most likely reason for this would be | |||
that you've not closed an output stream that was being used to write to file. | |||
If you find that something beyond your control is changing permissions on | |||
your temporary files and preventing them from being deleted, you may want to | |||
call TemporaryFile::deleteTemporaryFile() to detect those error cases and | |||
handle them appropriately. | |||
*/ | |||
jassertfalse; | |||
} | |||
} | |||
//============================================================================== | |||
bool TemporaryFile::overwriteTargetFileWithTemporary() const | |||
{ | |||
// This method only works if you created this object with the constructor | |||
// that takes a target file! | |||
jassert (targetFile != File()); | |||
if (temporaryFile.exists()) | |||
{ | |||
// Have a few attempts at overwriting the file before giving up.. | |||
for (int i = 5; --i >= 0;) | |||
{ | |||
if (temporaryFile.replaceFileIn (targetFile)) | |||
return true; | |||
Thread::sleep (100); | |||
} | |||
} | |||
else | |||
{ | |||
// There's no temporary file to use. If your write failed, you should | |||
// probably check, and not bother calling this method. | |||
jassertfalse; | |||
} | |||
return false; | |||
} | |||
bool TemporaryFile::deleteTemporaryFile() const | |||
{ | |||
// Have a few attempts at deleting the file before giving up.. | |||
for (int i = 5; --i >= 0;) | |||
{ | |||
if (temporaryFile.deleteFile()) | |||
return true; | |||
Thread::sleep (50); | |||
} | |||
return false; | |||
} |
@@ -0,0 +1,171 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_TEMPORARYFILE_H_INCLUDED | |||
#define JUCE_TEMPORARYFILE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Manages a temporary file, which will be deleted when this object is deleted. | |||
This object is intended to be used as a stack based object, using its scope | |||
to make sure the temporary file isn't left lying around. | |||
For example: | |||
@code | |||
{ | |||
File myTargetFile ("~/myfile.txt"); | |||
// this will choose a file called something like "~/myfile_temp239348.txt" | |||
// which definitely doesn't exist at the time the constructor is called. | |||
TemporaryFile temp (myTargetFile); | |||
// create a stream to the temporary file, and write some data to it... | |||
ScopedPointer<FileOutputStream> out (temp.getFile().createOutputStream()); | |||
if (out != nullptr) | |||
{ | |||
out->write ( ...etc ) | |||
out = nullptr; // (deletes the stream) | |||
// ..now we've finished writing, this will rename the temp file to | |||
// make it replace the target file we specified above. | |||
bool succeeded = temp.overwriteTargetFileWithTemporary(); | |||
} | |||
// ..and even if something went wrong and our overwrite failed, | |||
// as the TemporaryFile object goes out of scope here, it'll make sure | |||
// that the temp file gets deleted. | |||
} | |||
@endcode | |||
@see File, FileOutputStream | |||
*/ | |||
class JUCE_API TemporaryFile | |||
{ | |||
public: | |||
//============================================================================== | |||
enum OptionFlags | |||
{ | |||
useHiddenFile = 1, /**< Indicates that the temporary file should be hidden - | |||
i.e. its name should start with a dot. */ | |||
putNumbersInBrackets = 2 /**< Indicates that when numbers are appended to make sure | |||
the file is unique, they should go in brackets rather | |||
than just being appended (see File::getNonexistentSibling() )*/ | |||
}; | |||
//============================================================================== | |||
/** Creates a randomly-named temporary file in the default temp directory. | |||
@param suffix a file suffix to use for the file | |||
@param optionFlags a combination of the values listed in the OptionFlags enum | |||
The file will not be created until you write to it. And remember that when | |||
this object is deleted, the file will also be deleted! | |||
*/ | |||
TemporaryFile (const String& suffix = String(), | |||
int optionFlags = 0); | |||
/** Creates a temporary file in the same directory as a specified file. | |||
This is useful if you have a file that you want to overwrite, but don't | |||
want to harm the original file if the write operation fails. You can | |||
use this to create a temporary file next to the target file, then | |||
write to the temporary file, and finally use overwriteTargetFileWithTemporary() | |||
to replace the target file with the one you've just written. | |||
This class won't create any files until you actually write to them. And remember | |||
that when this object is deleted, the temporary file will also be deleted! | |||
@param targetFile the file that you intend to overwrite - the temporary | |||
file will be created in the same directory as this | |||
@param optionFlags a combination of the values listed in the OptionFlags enum | |||
*/ | |||
TemporaryFile (const File& targetFile, | |||
int optionFlags = 0); | |||
/** Creates a temporary file using an explicit filename. | |||
The other constructors are a better choice than this one, unless for some reason | |||
you need to explicitly specify the temporary file you want to use. | |||
@param targetFile the file that you intend to overwrite | |||
@param temporaryFile the temporary file to be used | |||
*/ | |||
TemporaryFile (const File& targetFile, | |||
const File& temporaryFile); | |||
/** Destructor. | |||
When this object is deleted it will make sure that its temporary file is | |||
also deleted! If the operation fails, it'll throw an assertion in debug | |||
mode. | |||
*/ | |||
~TemporaryFile(); | |||
//============================================================================== | |||
/** Returns the temporary file. */ | |||
const File& getFile() const noexcept { return temporaryFile; } | |||
/** Returns the target file that was specified in the constructor. */ | |||
const File& getTargetFile() const noexcept { return targetFile; } | |||
/** Tries to move the temporary file to overwrite the target file that was | |||
specified in the constructor. | |||
If you used the constructor that specified a target file, this will attempt | |||
to replace that file with the temporary one. | |||
Before calling this, make sure: | |||
- that you've actually written to the temporary file | |||
- that you've closed any open streams that you were using to write to it | |||
- and that you don't have any streams open to the target file, which would | |||
prevent it being overwritten | |||
If the file move succeeds, this returns false, and the temporary file will | |||
have disappeared. If it fails, the temporary file will probably still exist, | |||
but will be deleted when this object is destroyed. | |||
*/ | |||
bool overwriteTargetFileWithTemporary() const; | |||
/** Attempts to delete the temporary file, if it exists. | |||
@returns true if the file is successfully deleted (or if it didn't exist). | |||
*/ | |||
bool deleteTemporaryFile() const; | |||
private: | |||
//============================================================================== | |||
const File temporaryFile, targetFile; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryFile) | |||
}; | |||
#endif // JUCE_TEMPORARYFILE_H_INCLUDED |
@@ -18,13 +18,6 @@ | |||
#include "juce_audio_graph.h" | |||
// #include "AppConfig.h" | |||
// #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
// #include "juce_audio_processors.h" | |||
// #include <juce_gui_extra/juce_gui_extra.h> | |||
#include <locale> | |||
#include <cctype> | |||
#include <cstdarg> | |||
@@ -43,33 +36,13 @@ namespace juce2 | |||
// #include "streams/juce_InputStream.cpp" | |||
// #include "streams/juce_OutputStream.cpp" | |||
// static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||
// const PluginDescription& desc) | |||
// { | |||
// for (int i = list.size(); --i >= 0;) | |||
// if (list.getUnchecked(i)->isDuplicateOf (desc)) | |||
// return true; | |||
// | |||
// return false; | |||
// } | |||
#include "midi/juce_MidiBuffer.cpp" | |||
#include "midi/juce_MidiMessage.cpp" | |||
// #include "format/juce_AudioPluginFormat.cpp" | |||
// #include "format/juce_AudioPluginFormatManager.cpp" | |||
#include "processors/juce_AudioProcessor.cpp" | |||
#include "processors/juce_AudioProcessorGraph.cpp" | |||
// #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
// #include "processors/juce_PluginDescription.cpp" | |||
// #include "format_types/juce_LADSPAPluginFormat.cpp" | |||
// #include "format_types/juce_VSTPluginFormat.cpp" | |||
// #include "format_types/juce_VST3PluginFormat.cpp" | |||
// #include "format_types/juce_AudioUnitPluginFormat.mm" | |||
// #include "scanning/juce_KnownPluginList.cpp" | |||
// #include "scanning/juce_PluginDirectoryScanner.cpp" | |||
// #include "scanning/juce_PluginListComponent.cpp" | |||
// #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
// #include "utilities/juce_AudioProcessorParameters.cpp" | |||
#include "xml/juce_XmlElement.cpp" | |||
#include "xml/juce_XmlDocument.cpp" | |||
} |
@@ -109,11 +109,18 @@ | |||
namespace juce2 | |||
{ | |||
class DynamicObject; | |||
class File; | |||
class FileInputStream; | |||
class FileOutputStream; | |||
class InputSource; | |||
class InputStream; | |||
class MidiMessage; | |||
class MemoryBlock; | |||
class OutputStream; | |||
class Result; | |||
class StringRef; | |||
class XmlElement; | |||
#include "memory/juce_Memory.h" | |||
#include "maths/juce_MathsFunctions.h" | |||
@@ -123,47 +130,51 @@ class StringRef; | |||
#include "text/juce_CharPointer_UTF8.h" | |||
#include "text/juce_String.h" | |||
#include "text/juce_StringRef.h" | |||
#include "memory/juce_HeapBlock.h" | |||
#include "containers/juce_ElementComparator.h" | |||
#include "containers/juce_ArrayAllocationBase.h" | |||
#include "containers/juce_Array.h" | |||
#include "text/juce_StringArray.h" | |||
#include "text/juce_StringRef.h" | |||
#include "memory/juce_MemoryBlock.h" | |||
#include "memory/juce_ReferenceCountedObject.h" | |||
#include "text/juce_Identifier.h" | |||
#include "text/juce_NewLine.h" | |||
#include "containers/juce_ElementComparator.h" | |||
#include "containers/juce_ArrayAllocationBase.h" | |||
#include "containers/juce_Array.h" | |||
#include "containers/juce_LinkedListPointer.h" | |||
#include "containers/juce_OwnedArray.h" | |||
#include "containers/juce_ReferenceCountedArray.h" | |||
#include "containers/juce_SortedSet.h" | |||
// #include "containers/juce_Variant.h" | |||
// #include "containers/juce_NamedValueSet.h" | |||
#include "containers/juce_Variant.h" | |||
#include "containers/juce_NamedValueSet.h" | |||
#include "streams/juce_InputSource.h" | |||
#include "streams/juce_InputStream.h" | |||
#include "streams/juce_OutputStream.h" | |||
#include "streams/juce_MemoryOutputStream.h" | |||
#include "misc/juce_Result.h" | |||
#include "text/juce_StringPool.h" | |||
#include "files/juce_File.h" | |||
#include "streams/juce_FileInputStream.h" | |||
#include "streams/juce_FileInputSource.h" | |||
#include "buffers/juce_AudioSampleBuffer.h" | |||
#include "midi/juce_MidiBuffer.h" | |||
#include "midi/juce_MidiMessage.h" | |||
class AudioProcessor; | |||
#include "processors/juce_AudioPlayHead.h" | |||
// #include "processors/juce_AudioProcessorListener.h" | |||
// #include "processors/juce_AudioProcessorParameter.h" | |||
#include "processors/juce_AudioProcessor.h" | |||
// #include "processors/juce_PluginDescription.h" | |||
// #include "processors/juce_AudioPluginInstance.h" | |||
#include "processors/juce_AudioProcessorGraph.h" | |||
// #include "format/juce_AudioPluginFormat.h" | |||
// #include "format/juce_AudioPluginFormatManager.h" | |||
// #include "scanning/juce_KnownPluginList.h" | |||
// #include "utilities/juce_AudioProcessorValueTreeState.h" | |||
// #include "utilities/juce_AudioProcessorParameterWithID.h" | |||
// #include "utilities/juce_AudioParameterFloat.h" | |||
// #include "utilities/juce_AudioParameterInt.h" | |||
// #include "utilities/juce_AudioParameterBool.h" | |||
// #include "utilities/juce_AudioParameterChoice.h" | |||
#include "xml/juce_XmlElement.h" | |||
#include "xml/juce_XmlDocument.h" | |||
} | |||
@@ -0,0 +1,24 @@ | |||
#include "juce_audio_graph.h" | |||
#include <iostream> | |||
using namespace juce2; | |||
int main() | |||
{ | |||
String x = "haha"; | |||
std::cout << x << std::endl; | |||
Atomic<float> a; | |||
CharPointer_UTF8 c("c"); | |||
HeapBlock<String> hs; | |||
MemoryBlock m; | |||
Array<CharPointer_UTF8> ar; | |||
OwnedArray<String> ows; | |||
AudioSampleBuffer as; | |||
MidiBuffer mb; | |||
MidiMessage ms; | |||
AudioProcessorGraph g; | |||
return 0; | |||
} |
@@ -0,0 +1,423 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||
#define JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A base class which provides methods for reference-counting. | |||
To add reference-counting to a class, derive it from this class, and | |||
use the ReferenceCountedObjectPtr class to point to it. | |||
e.g. @code | |||
class MyClass : public ReferenceCountedObject | |||
{ | |||
void foo(); | |||
// This is a neat way of declaring a typedef for a pointer class, | |||
// rather than typing out the full templated name each time.. | |||
typedef ReferenceCountedObjectPtr<MyClass> Ptr; | |||
}; | |||
MyClass::Ptr p = new MyClass(); | |||
MyClass::Ptr p2 = p; | |||
p = nullptr; | |||
p2->foo(); | |||
@endcode | |||
Once a new ReferenceCountedObject has been assigned to a pointer, be | |||
careful not to delete the object manually. | |||
This class uses an Atomic<int> value to hold the reference count, so that it | |||
the pointers can be passed between threads safely. For a faster but non-thread-safe | |||
version, use SingleThreadedReferenceCountedObject instead. | |||
@see ReferenceCountedObjectPtr, ReferenceCountedArray, SingleThreadedReferenceCountedObject | |||
*/ | |||
class JUCE_API ReferenceCountedObject | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Increments the object's reference count. | |||
This is done automatically by the smart pointer, but is public just | |||
in case it's needed for nefarious purposes. | |||
*/ | |||
void incReferenceCount() noexcept | |||
{ | |||
++refCount; | |||
} | |||
/** Decreases the object's reference count. | |||
If the count gets to zero, the object will be deleted. | |||
*/ | |||
void decReferenceCount() noexcept | |||
{ | |||
jassert (getReferenceCount() > 0); | |||
if (--refCount == 0) | |||
delete this; | |||
} | |||
/** Decreases the object's reference count. | |||
If the count gets to zero, the object will not be deleted, but this method | |||
will return true, allowing the caller to take care of deletion. | |||
*/ | |||
bool decReferenceCountWithoutDeleting() noexcept | |||
{ | |||
jassert (getReferenceCount() > 0); | |||
return --refCount == 0; | |||
} | |||
/** Returns the object's current reference count. */ | |||
int getReferenceCount() const noexcept { return refCount.get(); } | |||
protected: | |||
//============================================================================== | |||
/** Creates the reference-counted object (with an initial ref count of zero). */ | |||
ReferenceCountedObject() {} | |||
/** Destructor. */ | |||
virtual ~ReferenceCountedObject() | |||
{ | |||
// it's dangerous to delete an object that's still referenced by something else! | |||
jassert (getReferenceCount() == 0); | |||
} | |||
/** Resets the reference count to zero without deleting the object. | |||
You should probably never need to use this! | |||
*/ | |||
void resetReferenceCount() noexcept | |||
{ | |||
refCount = 0; | |||
} | |||
private: | |||
//============================================================================== | |||
Atomic <int> refCount; | |||
JUCE_DECLARE_NON_COPYABLE (ReferenceCountedObject) | |||
}; | |||
//============================================================================== | |||
/** | |||
Adds reference-counting to an object. | |||
This is effectively a version of the ReferenceCountedObject class, but which | |||
uses a non-atomic counter, and so is not thread-safe (but which will be more | |||
efficient). | |||
For more details on how to use it, see the ReferenceCountedObject class notes. | |||
@see ReferenceCountedObject, ReferenceCountedObjectPtr, ReferenceCountedArray | |||
*/ | |||
class JUCE_API SingleThreadedReferenceCountedObject | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Increments the object's reference count. | |||
This is done automatically by the smart pointer, but is public just | |||
in case it's needed for nefarious purposes. | |||
*/ | |||
void incReferenceCount() noexcept | |||
{ | |||
++refCount; | |||
} | |||
/** Decreases the object's reference count. | |||
If the count gets to zero, the object will be deleted. | |||
*/ | |||
void decReferenceCount() noexcept | |||
{ | |||
jassert (getReferenceCount() > 0); | |||
if (--refCount == 0) | |||
delete this; | |||
} | |||
/** Decreases the object's reference count. | |||
If the count gets to zero, the object will not be deleted, but this method | |||
will return true, allowing the caller to take care of deletion. | |||
*/ | |||
bool decReferenceCountWithoutDeleting() noexcept | |||
{ | |||
jassert (getReferenceCount() > 0); | |||
return --refCount == 0; | |||
} | |||
/** Returns the object's current reference count. */ | |||
int getReferenceCount() const noexcept { return refCount; } | |||
protected: | |||
//============================================================================== | |||
/** Creates the reference-counted object (with an initial ref count of zero). */ | |||
SingleThreadedReferenceCountedObject() : refCount (0) {} | |||
/** Destructor. */ | |||
virtual ~SingleThreadedReferenceCountedObject() | |||
{ | |||
// it's dangerous to delete an object that's still referenced by something else! | |||
jassert (getReferenceCount() == 0); | |||
} | |||
private: | |||
//============================================================================== | |||
int refCount; | |||
JUCE_DECLARE_NON_COPYABLE (SingleThreadedReferenceCountedObject) | |||
}; | |||
//============================================================================== | |||
/** | |||
A smart-pointer class which points to a reference-counted object. | |||
The template parameter specifies the class of the object you want to point to - the easiest | |||
way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject | |||
or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||
class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||
decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||
should behave. | |||
When using this class, you'll probably want to create a typedef to abbreviate the full | |||
templated name - e.g. | |||
@code | |||
struct MyClass : public ReferenceCountedObject | |||
{ | |||
typedef ReferenceCountedObjectPtr<MyClass> Ptr; | |||
... | |||
@endcode | |||
@see ReferenceCountedObject, ReferenceCountedObjectArray | |||
*/ | |||
template <class ReferenceCountedObjectClass> | |||
class ReferenceCountedObjectPtr | |||
{ | |||
public: | |||
/** The class being referenced by this pointer. */ | |||
typedef ReferenceCountedObjectClass ReferencedType; | |||
//============================================================================== | |||
/** Creates a pointer to a null object. */ | |||
ReferenceCountedObjectPtr() noexcept | |||
: referencedObject (nullptr) | |||
{ | |||
} | |||
/** Creates a pointer to an object. | |||
This will increment the object's reference-count. | |||
*/ | |||
ReferenceCountedObjectPtr (ReferencedType* refCountedObject) noexcept | |||
: referencedObject (refCountedObject) | |||
{ | |||
incIfNotNull (refCountedObject); | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_NULLPTR | |||
/** Creates a pointer to a null object. */ | |||
ReferenceCountedObjectPtr (decltype (nullptr)) noexcept | |||
: referencedObject (nullptr) | |||
{ | |||
} | |||
#endif | |||
/** Copies another pointer. | |||
This will increment the object's reference-count. | |||
*/ | |||
ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept | |||
: referencedObject (other.referencedObject) | |||
{ | |||
incIfNotNull (referencedObject); | |||
} | |||
/** Copies another pointer. | |||
This will increment the object's reference-count (if it is non-null). | |||
*/ | |||
template <typename Convertible> | |||
ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr<Convertible>& other) noexcept | |||
: referencedObject (static_cast<ReferencedType*> (other.get())) | |||
{ | |||
incIfNotNull (referencedObject); | |||
} | |||
/** Changes this pointer to point at a different object. | |||
The reference count of the old object is decremented, and it might be | |||
deleted if it hits zero. The new object's count is incremented. | |||
*/ | |||
ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) | |||
{ | |||
return operator= (other.referencedObject); | |||
} | |||
/** Changes this pointer to point at a different object. | |||
The reference count of the old object is decremented, and it might be | |||
deleted if it hits zero. The new object's count is incremented. | |||
*/ | |||
template <typename Convertible> | |||
ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr<Convertible>& other) | |||
{ | |||
return operator= (static_cast<ReferencedType*> (other.get())); | |||
} | |||
/** Changes this pointer to point at a different object. | |||
The reference count of the old object is decremented, and it might be | |||
deleted if it hits zero. The new object's count is incremented. | |||
*/ | |||
ReferenceCountedObjectPtr& operator= (ReferencedType* const newObject) | |||
{ | |||
if (referencedObject != newObject) | |||
{ | |||
incIfNotNull (newObject); | |||
ReferencedType* const oldObject = referencedObject; | |||
referencedObject = newObject; | |||
decIfNotNull (oldObject); | |||
} | |||
return *this; | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
/** Takes-over the object from another pointer. */ | |||
ReferenceCountedObjectPtr (ReferenceCountedObjectPtr&& other) noexcept | |||
: referencedObject (other.referencedObject) | |||
{ | |||
other.referencedObject = nullptr; | |||
} | |||
/** Takes-over the object from another pointer. */ | |||
ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectPtr&& other) | |||
{ | |||
std::swap (referencedObject, other.referencedObject); | |||
return *this; | |||
} | |||
#endif | |||
/** Destructor. | |||
This will decrement the object's reference-count, which will cause the | |||
object to be deleted when the ref-count hits zero. | |||
*/ | |||
~ReferenceCountedObjectPtr() | |||
{ | |||
decIfNotNull (referencedObject); | |||
} | |||
//============================================================================== | |||
/** Returns the object that this pointer references. | |||
The pointer returned may be null, of course. | |||
*/ | |||
operator ReferencedType*() const noexcept { return referencedObject; } | |||
/** Returns the object that this pointer references. | |||
The pointer returned may be null, of course. | |||
*/ | |||
ReferencedType* get() const noexcept { return referencedObject; } | |||
/** Returns the object that this pointer references. | |||
The pointer returned may be null, of course. | |||
*/ | |||
ReferencedType* getObject() const noexcept { return referencedObject; } | |||
// the -> operator is called on the referenced object | |||
ReferencedType* operator->() const noexcept | |||
{ | |||
jassert (referencedObject != nullptr); // null pointer method call! | |||
return referencedObject; | |||
} | |||
private: | |||
//============================================================================== | |||
ReferencedType* referencedObject; | |||
static void incIfNotNull (ReferencedType* o) noexcept | |||
{ | |||
if (o != nullptr) | |||
o->incReferenceCount(); | |||
} | |||
static void decIfNotNull (ReferencedType* o) noexcept | |||
{ | |||
if (o != nullptr && o->decReferenceCountWithoutDeleting()) | |||
delete o; | |||
} | |||
}; | |||
//============================================================================== | |||
/** Compares two ReferenceCountedObjectPtrs. */ | |||
template <typename ReferenceCountedObjectClass> | |||
bool operator== (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, ReferenceCountedObjectClass* const object2) noexcept | |||
{ | |||
return object1.get() == object2; | |||
} | |||
/** Compares two ReferenceCountedObjectPtrs. */ | |||
template <typename ReferenceCountedObjectClass> | |||
bool operator== (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
{ | |||
return object1.get() == object2.get(); | |||
} | |||
/** Compares two ReferenceCountedObjectPtrs. */ | |||
template <typename ReferenceCountedObjectClass> | |||
bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
{ | |||
return object1 == object2.get(); | |||
} | |||
/** Compares two ReferenceCountedObjectPtrs. */ | |||
template <typename ReferenceCountedObjectClass> | |||
bool operator!= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectClass* object2) noexcept | |||
{ | |||
return object1.get() != object2; | |||
} | |||
/** Compares two ReferenceCountedObjectPtrs. */ | |||
template <typename ReferenceCountedObjectClass> | |||
bool operator!= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
{ | |||
return object1.get() != object2.get(); | |||
} | |||
/** Compares two ReferenceCountedObjectPtrs. */ | |||
template <typename ReferenceCountedObjectClass> | |||
bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& object2) noexcept | |||
{ | |||
return object1 != object2.get(); | |||
} | |||
#endif // JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED |
@@ -0,0 +1,85 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
Result::Result() noexcept {} | |||
Result::Result (const String& message) noexcept | |||
: errorMessage (message) | |||
{ | |||
} | |||
Result::Result (const Result& other) | |||
: errorMessage (other.errorMessage) | |||
{ | |||
} | |||
Result& Result::operator= (const Result& other) | |||
{ | |||
errorMessage = other.errorMessage; | |||
return *this; | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
Result::Result (Result&& other) noexcept | |||
: errorMessage (static_cast<String&&> (other.errorMessage)) | |||
{ | |||
} | |||
Result& Result::operator= (Result&& other) noexcept | |||
{ | |||
errorMessage = static_cast<String&&> (other.errorMessage); | |||
return *this; | |||
} | |||
#endif | |||
bool Result::operator== (const Result& other) const noexcept | |||
{ | |||
return errorMessage == other.errorMessage; | |||
} | |||
bool Result::operator!= (const Result& other) const noexcept | |||
{ | |||
return errorMessage != other.errorMessage; | |||
} | |||
Result Result::fail (const String& errorMessage) noexcept | |||
{ | |||
return Result (errorMessage.isEmpty() ? "Unknown Error" : errorMessage); | |||
} | |||
const String& Result::getErrorMessage() const noexcept | |||
{ | |||
return errorMessage; | |||
} | |||
bool Result::wasOk() const noexcept { return errorMessage.isEmpty(); } | |||
Result::operator bool() const noexcept { return errorMessage.isEmpty(); } | |||
bool Result::failed() const noexcept { return errorMessage.isNotEmpty(); } | |||
bool Result::operator!() const noexcept { return errorMessage.isNotEmpty(); } |
@@ -0,0 +1,127 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_RESULT_H_INCLUDED | |||
#define JUCE_RESULT_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Represents the 'success' or 'failure' of an operation, and holds an associated | |||
error message to describe the error when there's a failure. | |||
E.g. | |||
@code | |||
Result myOperation() | |||
{ | |||
if (doSomeKindOfFoobar()) | |||
return Result::ok(); | |||
else | |||
return Result::fail ("foobar didn't work!"); | |||
} | |||
const Result result (myOperation()); | |||
if (result.wasOk()) | |||
{ | |||
...it's all good... | |||
} | |||
else | |||
{ | |||
warnUserAboutFailure ("The foobar operation failed! Error message was: " | |||
+ result.getErrorMessage()); | |||
} | |||
@endcode | |||
*/ | |||
class JUCE_API Result | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates and returns a 'successful' result. */ | |||
static Result ok() noexcept { return Result(); } | |||
/** Creates a 'failure' result. | |||
If you pass a blank error message in here, a default "Unknown Error" message | |||
will be used instead. | |||
*/ | |||
static Result fail (const String& errorMessage) noexcept; | |||
//============================================================================== | |||
/** Returns true if this result indicates a success. */ | |||
bool wasOk() const noexcept; | |||
/** Returns true if this result indicates a failure. | |||
You can use getErrorMessage() to retrieve the error message associated | |||
with the failure. | |||
*/ | |||
bool failed() const noexcept; | |||
/** Returns true if this result indicates a success. | |||
This is equivalent to calling wasOk(). | |||
*/ | |||
operator bool() const noexcept; | |||
/** Returns true if this result indicates a failure. | |||
This is equivalent to calling failed(). | |||
*/ | |||
bool operator!() const noexcept; | |||
/** Returns the error message that was set when this result was created. | |||
For a successful result, this will be an empty string; | |||
*/ | |||
const String& getErrorMessage() const noexcept; | |||
//============================================================================== | |||
Result (const Result&); | |||
Result& operator= (const Result&); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
Result (Result&&) noexcept; | |||
Result& operator= (Result&&) noexcept; | |||
#endif | |||
bool operator== (const Result& other) const noexcept; | |||
bool operator!= (const Result& other) const noexcept; | |||
private: | |||
String errorMessage; | |||
// The default constructor is not for public use! | |||
// Instead, use Result::ok() or Result::fail() | |||
Result() noexcept; | |||
explicit Result (const String&) noexcept; | |||
// These casts are private to prevent people trying to use the Result object in numeric contexts | |||
operator int() const; | |||
operator void*() const; | |||
}; | |||
#endif // JUCE_RESULT_H_INCLUDED |
@@ -71,7 +71,6 @@ public: | |||
/** The actual processor object that this node represents. */ | |||
AudioProcessor* getProcessor() const noexcept { return processor; } | |||
#if 0 | |||
/** A set of user-definable properties that are associated with this node. | |||
This can be used to attach values to the node for whatever purpose seems | |||
@@ -79,7 +78,6 @@ public: | |||
is displaying the nodes on-screen. | |||
*/ | |||
NamedValueSet properties; | |||
#endif | |||
//============================================================================== | |||
/** A convenient typedef for referring to a pointer to a node object. */ | |||
@@ -0,0 +1,58 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
FileInputSource::FileInputSource (const File& f, bool useFileTimeInHash) | |||
: file (f), useFileTimeInHashGeneration (useFileTimeInHash) | |||
{ | |||
} | |||
FileInputSource::~FileInputSource() | |||
{ | |||
} | |||
InputStream* FileInputSource::createInputStream() | |||
{ | |||
return file.createInputStream(); | |||
} | |||
InputStream* FileInputSource::createInputStreamFor (const String& relatedItemPath) | |||
{ | |||
return file.getSiblingFile (relatedItemPath).createInputStream(); | |||
} | |||
int64 FileInputSource::hashCode() const | |||
{ | |||
int64 h = file.hashCode(); | |||
if (useFileTimeInHashGeneration) | |||
h ^= file.getLastModificationTime().toMilliseconds(); | |||
return h; | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_FILEINPUTSOURCE_H_INCLUDED | |||
#define JUCE_FILEINPUTSOURCE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A type of InputSource that represents a normal file. | |||
@see InputSource | |||
*/ | |||
class JUCE_API FileInputSource : public InputSource | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a FileInputSource for a file. | |||
If the useFileTimeInHashGeneration parameter is true, then this object's | |||
hashCode() method will incorporate the file time into its hash code; if | |||
false, only the file name will be used for the hash. | |||
*/ | |||
FileInputSource (const File& file, bool useFileTimeInHashGeneration = false); | |||
/** Destructor. */ | |||
~FileInputSource(); | |||
InputStream* createInputStream(); | |||
InputStream* createInputStreamFor (const String& relatedItemPath); | |||
int64 hashCode() const; | |||
private: | |||
//============================================================================== | |||
const File file; | |||
bool useFileTimeInHashGeneration; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputSource) | |||
}; | |||
#endif // JUCE_FILEINPUTSOURCE_H_INCLUDED |
@@ -0,0 +1,86 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
int64 juce_fileSetPosition (void* handle, int64 pos); | |||
//============================================================================== | |||
FileInputStream::FileInputStream (const File& f) | |||
: file (f), | |||
fileHandle (nullptr), | |||
currentPosition (0), | |||
status (Result::ok()) | |||
{ | |||
openHandle(); | |||
} | |||
int64 FileInputStream::getTotalLength() | |||
{ | |||
// You should always check that a stream opened successfully before using it! | |||
jassert (openedOk()); | |||
return file.getSize(); | |||
} | |||
int FileInputStream::read (void* buffer, int bytesToRead) | |||
{ | |||
// You should always check that a stream opened successfully before using it! | |||
jassert (openedOk()); | |||
// The buffer should never be null, and a negative size is probably a | |||
// sign that something is broken! | |||
jassert (buffer != nullptr && bytesToRead >= 0); | |||
const size_t num = readInternal (buffer, (size_t) bytesToRead); | |||
currentPosition += (int64) num; | |||
return (int) num; | |||
} | |||
bool FileInputStream::isExhausted() | |||
{ | |||
return currentPosition >= getTotalLength(); | |||
} | |||
int64 FileInputStream::getPosition() | |||
{ | |||
return currentPosition; | |||
} | |||
bool FileInputStream::setPosition (int64 pos) | |||
{ | |||
// You should always check that a stream opened successfully before using it! | |||
jassert (openedOk()); | |||
if (pos != currentPosition) | |||
currentPosition = juce_fileSetPosition (fileHandle, pos); | |||
return currentPosition == pos; | |||
} |
@@ -0,0 +1,98 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_FILEINPUTSTREAM_H_INCLUDED | |||
#define JUCE_FILEINPUTSTREAM_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
An input stream that reads from a local file. | |||
@see InputStream, FileOutputStream, File::createInputStream | |||
*/ | |||
class JUCE_API FileInputStream : public InputStream | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a FileInputStream to read from the given file. | |||
After creating a FileInputStream, you should use openedOk() or failedToOpen() | |||
to make sure that it's OK before trying to read from it! If it failed, you | |||
can call getStatus() to get more error information. | |||
*/ | |||
explicit FileInputStream (const File& fileToRead); | |||
/** Destructor. */ | |||
~FileInputStream(); | |||
//============================================================================== | |||
/** Returns the file that this stream is reading from. */ | |||
const File& getFile() const noexcept { return file; } | |||
/** Returns the status of the file stream. | |||
The result will be ok if the file opened successfully. If an error occurs while | |||
opening or reading from the file, this will contain an error message. | |||
*/ | |||
const Result& getStatus() const noexcept { return status; } | |||
/** Returns true if the stream couldn't be opened for some reason. | |||
@see getResult() | |||
*/ | |||
bool failedToOpen() const noexcept { return status.failed(); } | |||
/** Returns true if the stream opened without problems. | |||
@see getResult() | |||
*/ | |||
bool openedOk() const noexcept { return status.wasOk(); } | |||
//============================================================================== | |||
int64 getTotalLength() override; | |||
int read (void*, int) override; | |||
bool isExhausted() override; | |||
int64 getPosition() override; | |||
bool setPosition (int64) override; | |||
private: | |||
//============================================================================== | |||
const File file; | |||
void* fileHandle; | |||
int64 currentPosition; | |||
Result status; | |||
void openHandle(); | |||
size_t readInternal (void*, size_t); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) | |||
}; | |||
#endif // JUCE_FILEINPUTSTREAM_H_INCLUDED |
@@ -0,0 +1,80 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_INPUTSOURCE_H_INCLUDED | |||
#define JUCE_INPUTSOURCE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A lightweight object that can create a stream to read some kind of resource. | |||
This may be used to refer to a file, or some other kind of source, allowing a | |||
caller to create an input stream that can read from it when required. | |||
@see FileInputSource | |||
*/ | |||
class JUCE_API InputSource | |||
{ | |||
public: | |||
//============================================================================== | |||
InputSource() noexcept {} | |||
/** Destructor. */ | |||
virtual ~InputSource() {} | |||
//============================================================================== | |||
/** Returns a new InputStream to read this item. | |||
@returns an inputstream that the caller will delete, or nullptr if | |||
the filename isn't found. | |||
*/ | |||
virtual InputStream* createInputStream() = 0; | |||
/** Returns a new InputStream to read an item, relative. | |||
@param relatedItemPath the relative pathname of the resource that is required | |||
@returns an inputstream that the caller will delete, or nullptr if | |||
the item isn't found. | |||
*/ | |||
virtual InputStream* createInputStreamFor (const String& relatedItemPath) = 0; | |||
/** Returns a hash code that uniquely represents this item. | |||
*/ | |||
virtual int64 hashCode() const = 0; | |||
private: | |||
//============================================================================== | |||
JUCE_LEAK_DETECTOR (InputSource) | |||
}; | |||
#endif // JUCE_INPUTSOURCE_H_INCLUDED |
@@ -0,0 +1,79 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
Identifier::Identifier() noexcept {} | |||
Identifier::~Identifier() noexcept {} | |||
Identifier::Identifier (const Identifier& other) noexcept : name (other.name) {} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
Identifier::Identifier (Identifier&& other) noexcept : name (static_cast<String&&> (other.name)) {} | |||
Identifier& Identifier::operator= (Identifier&& other) noexcept | |||
{ | |||
name = static_cast<String&&> (other.name); | |||
return *this; | |||
} | |||
#endif | |||
Identifier& Identifier::operator= (const Identifier& other) noexcept | |||
{ | |||
name = other.name; | |||
return *this; | |||
} | |||
Identifier::Identifier (const String& nm) | |||
: name (StringPool::getGlobalPool().getPooledString (nm)) | |||
{ | |||
// An Identifier cannot be created from an empty string! | |||
jassert (nm.isNotEmpty()); | |||
} | |||
Identifier::Identifier (const char* nm) | |||
: name (StringPool::getGlobalPool().getPooledString (nm)) | |||
{ | |||
// An Identifier cannot be created from an empty string! | |||
jassert (nm != nullptr && nm[0] != 0); | |||
} | |||
Identifier::Identifier (String::CharPointerType start, String::CharPointerType end) | |||
: name (StringPool::getGlobalPool().getPooledString (start, end)) | |||
{ | |||
// An Identifier cannot be created from an empty string! | |||
jassert (start < end); | |||
} | |||
Identifier Identifier::null; | |||
bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept | |||
{ | |||
return possibleIdentifier.isNotEmpty() | |||
&& possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%"); | |||
} |
@@ -0,0 +1,142 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_IDENTIFIER_H_INCLUDED | |||
#define JUCE_IDENTIFIER_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Represents a string identifier, designed for accessing properties by name. | |||
Comparing two Identifier objects is very fast (an O(1) operation), but creating | |||
them can be slower than just using a String directly, so the optimal way to use them | |||
is to keep some static Identifier objects for the things you use often. | |||
@see NamedValueSet, ValueTree | |||
*/ | |||
class JUCE_API Identifier | |||
{ | |||
public: | |||
/** Creates a null identifier. */ | |||
Identifier() noexcept; | |||
/** Creates an identifier with a specified name. | |||
Because this name may need to be used in contexts such as script variables or XML | |||
tags, it must only contain ascii letters and digits, or the underscore character. | |||
*/ | |||
Identifier (const char* name); | |||
/** Creates an identifier with a specified name. | |||
Because this name may need to be used in contexts such as script variables or XML | |||
tags, it must only contain ascii letters and digits, or the underscore character. | |||
*/ | |||
Identifier (const String& name); | |||
/** Creates an identifier with a specified name. | |||
Because this name may need to be used in contexts such as script variables or XML | |||
tags, it must only contain ascii letters and digits, or the underscore character. | |||
*/ | |||
Identifier (String::CharPointerType nameStart, String::CharPointerType nameEnd); | |||
/** Creates a copy of another identifier. */ | |||
Identifier (const Identifier& other) noexcept; | |||
/** Creates a copy of another identifier. */ | |||
Identifier& operator= (const Identifier& other) noexcept; | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
/** Creates a copy of another identifier. */ | |||
Identifier (Identifier&& other) noexcept; | |||
/** Creates a copy of another identifier. */ | |||
Identifier& operator= (Identifier&& other) noexcept; | |||
#endif | |||
/** Destructor */ | |||
~Identifier() noexcept; | |||
/** Compares two identifiers. This is a very fast operation. */ | |||
inline bool operator== (const Identifier& other) const noexcept { return name.getCharPointer() == other.name.getCharPointer(); } | |||
/** Compares two identifiers. This is a very fast operation. */ | |||
inline bool operator!= (const Identifier& other) const noexcept { return name.getCharPointer() != other.name.getCharPointer(); } | |||
/** Compares the identifier with a string. */ | |||
inline bool operator== (StringRef other) const noexcept { return name == other; } | |||
/** Compares the identifier with a string. */ | |||
inline bool operator!= (StringRef other) const noexcept { return name != other; } | |||
/** Compares the identifier with a string. */ | |||
inline bool operator< (StringRef other) const noexcept { return name < other; } | |||
/** Compares the identifier with a string. */ | |||
inline bool operator<= (StringRef other) const noexcept { return name <= other; } | |||
/** Compares the identifier with a string. */ | |||
inline bool operator> (StringRef other) const noexcept { return name > other; } | |||
/** Compares the identifier with a string. */ | |||
inline bool operator>= (StringRef other) const noexcept { return name >= other; } | |||
/** Returns this identifier as a string. */ | |||
const String& toString() const noexcept { return name; } | |||
/** Returns this identifier's raw string pointer. */ | |||
operator String::CharPointerType() const noexcept { return name.getCharPointer(); } | |||
/** Returns this identifier's raw string pointer. */ | |||
String::CharPointerType getCharPointer() const noexcept { return name.getCharPointer(); } | |||
/** Returns this identifier as a StringRef. */ | |||
operator StringRef() const noexcept { return name; } | |||
/** Returns true if this Identifier is not null */ | |||
bool isValid() const noexcept { return name.isNotEmpty(); } | |||
/** Returns true if this Identifier is null */ | |||
bool isNull() const noexcept { return name.isEmpty(); } | |||
/** A null identifier. */ | |||
static Identifier null; | |||
/** Checks a given string for characters that might not be valid in an Identifier. | |||
Since Identifiers are used as a script variables and XML attributes, they should only contain | |||
alphanumeric characters, underscores, or the '-' and ':' characters. | |||
*/ | |||
static bool isValidIdentifier (const String& possibleIdentifier) noexcept; | |||
private: | |||
String name; | |||
}; | |||
#endif // JUCE_IDENTIFIER_H_INCLUDED |
@@ -0,0 +1,508 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
StringArray::StringArray() noexcept | |||
{ | |||
} | |||
StringArray::StringArray (const StringArray& other) | |||
: strings (other.strings) | |||
{ | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
StringArray::StringArray (StringArray&& other) noexcept | |||
: strings (static_cast<Array <String>&&> (other.strings)) | |||
{ | |||
} | |||
#endif | |||
StringArray::StringArray (const String& firstValue) | |||
{ | |||
strings.add (firstValue); | |||
} | |||
StringArray::StringArray (const String* initialStrings, int numberOfStrings) | |||
{ | |||
strings.addArray (initialStrings, numberOfStrings); | |||
} | |||
StringArray::StringArray (const char* const* initialStrings) | |||
{ | |||
strings.addNullTerminatedArray (initialStrings); | |||
} | |||
StringArray::StringArray (const char* const* initialStrings, int numberOfStrings) | |||
{ | |||
strings.addArray (initialStrings, numberOfStrings); | |||
} | |||
StringArray::StringArray (const wchar_t* const* initialStrings) | |||
{ | |||
strings.addNullTerminatedArray (initialStrings); | |||
} | |||
StringArray::StringArray (const wchar_t* const* initialStrings, int numberOfStrings) | |||
{ | |||
strings.addArray (initialStrings, numberOfStrings); | |||
} | |||
StringArray& StringArray::operator= (const StringArray& other) | |||
{ | |||
strings = other.strings; | |||
return *this; | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
StringArray& StringArray::operator= (StringArray&& other) noexcept | |||
{ | |||
strings = static_cast<Array<String>&&> (other.strings); | |||
return *this; | |||
} | |||
#endif | |||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
StringArray::StringArray (const std::initializer_list<const char*>& stringList) | |||
{ | |||
strings.addArray (stringList); | |||
} | |||
#endif | |||
StringArray::~StringArray() | |||
{ | |||
} | |||
bool StringArray::operator== (const StringArray& other) const noexcept | |||
{ | |||
return strings == other.strings; | |||
} | |||
bool StringArray::operator!= (const StringArray& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
void StringArray::swapWith (StringArray& other) noexcept | |||
{ | |||
strings.swapWith (other.strings); | |||
} | |||
void StringArray::clear() | |||
{ | |||
strings.clear(); | |||
} | |||
void StringArray::clearQuick() | |||
{ | |||
strings.clearQuick(); | |||
} | |||
const String& StringArray::operator[] (const int index) const noexcept | |||
{ | |||
if (isPositiveAndBelow (index, strings.size())) | |||
return strings.getReference (index); | |||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
return String::empty; | |||
#else | |||
static String empty; | |||
return empty; | |||
#endif | |||
} | |||
String& StringArray::getReference (const int index) noexcept | |||
{ | |||
return strings.getReference (index); | |||
} | |||
void StringArray::add (const String& newString) | |||
{ | |||
strings.add (newString); | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
void StringArray::add (String&& stringToAdd) | |||
{ | |||
strings.add (static_cast<String&&> (stringToAdd)); | |||
} | |||
#endif | |||
void StringArray::insert (const int index, const String& newString) | |||
{ | |||
strings.insert (index, newString); | |||
} | |||
bool StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) | |||
{ | |||
if (contains (newString, ignoreCase)) | |||
return false; | |||
add (newString); | |||
return true; | |||
} | |||
void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) | |||
{ | |||
if (startIndex < 0) | |||
{ | |||
jassertfalse; | |||
startIndex = 0; | |||
} | |||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size()) | |||
numElementsToAdd = otherArray.size() - startIndex; | |||
while (--numElementsToAdd >= 0) | |||
strings.add (otherArray.strings.getReference (startIndex++)); | |||
} | |||
void StringArray::mergeArray (const StringArray& otherArray, const bool ignoreCase) | |||
{ | |||
for (int i = 0; i < otherArray.size(); ++i) | |||
addIfNotAlreadyThere (otherArray[i], ignoreCase); | |||
} | |||
void StringArray::set (const int index, const String& newString) | |||
{ | |||
strings.set (index, newString); | |||
} | |||
bool StringArray::contains (StringRef stringToLookFor, const bool ignoreCase) const | |||
{ | |||
return indexOf (stringToLookFor, ignoreCase) >= 0; | |||
} | |||
int StringArray::indexOf (StringRef stringToLookFor, const bool ignoreCase, int i) const | |||
{ | |||
if (i < 0) | |||
i = 0; | |||
const int numElements = size(); | |||
if (ignoreCase) | |||
{ | |||
for (; i < numElements; ++i) | |||
if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) | |||
return i; | |||
} | |||
else | |||
{ | |||
for (; i < numElements; ++i) | |||
if (stringToLookFor == strings.getReference (i)) | |||
return i; | |||
} | |||
return -1; | |||
} | |||
void StringArray::move (const int currentIndex, const int newIndex) noexcept | |||
{ | |||
strings.move (currentIndex, newIndex); | |||
} | |||
//============================================================================== | |||
void StringArray::remove (const int index) | |||
{ | |||
strings.remove (index); | |||
} | |||
void StringArray::removeString (StringRef stringToRemove, const bool ignoreCase) | |||
{ | |||
if (ignoreCase) | |||
{ | |||
for (int i = size(); --i >= 0;) | |||
if (strings.getReference(i).equalsIgnoreCase (stringToRemove)) | |||
strings.remove (i); | |||
} | |||
else | |||
{ | |||
for (int i = size(); --i >= 0;) | |||
if (stringToRemove == strings.getReference (i)) | |||
strings.remove (i); | |||
} | |||
} | |||
void StringArray::removeRange (int startIndex, int numberToRemove) | |||
{ | |||
strings.removeRange (startIndex, numberToRemove); | |||
} | |||
//============================================================================== | |||
void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) | |||
{ | |||
if (removeWhitespaceStrings) | |||
{ | |||
for (int i = size(); --i >= 0;) | |||
if (! strings.getReference(i).containsNonWhitespaceChars()) | |||
strings.remove (i); | |||
} | |||
else | |||
{ | |||
for (int i = size(); --i >= 0;) | |||
if (strings.getReference(i).isEmpty()) | |||
strings.remove (i); | |||
} | |||
} | |||
void StringArray::trim() | |||
{ | |||
for (int i = size(); --i >= 0;) | |||
{ | |||
String& s = strings.getReference(i); | |||
s = s.trim(); | |||
} | |||
} | |||
//============================================================================== | |||
struct InternalStringArrayComparator_CaseSensitive | |||
{ | |||
static int compareElements (String& s1, String& s2) noexcept { return s1.compare (s2); } | |||
}; | |||
struct InternalStringArrayComparator_CaseInsensitive | |||
{ | |||
static int compareElements (String& s1, String& s2) noexcept { return s1.compareIgnoreCase (s2); } | |||
}; | |||
struct InternalStringArrayComparator_Natural | |||
{ | |||
static int compareElements (String& s1, String& s2) noexcept { return s1.compareNatural (s2); } | |||
}; | |||
void StringArray::sort (const bool ignoreCase) | |||
{ | |||
if (ignoreCase) | |||
{ | |||
InternalStringArrayComparator_CaseInsensitive comp; | |||
strings.sort (comp); | |||
} | |||
else | |||
{ | |||
InternalStringArrayComparator_CaseSensitive comp; | |||
strings.sort (comp); | |||
} | |||
} | |||
void StringArray::sortNatural() | |||
{ | |||
InternalStringArrayComparator_Natural comp; | |||
strings.sort (comp); | |||
} | |||
//============================================================================== | |||
String StringArray::joinIntoString (StringRef separator, int start, int numberToJoin) const | |||
{ | |||
const int last = (numberToJoin < 0) ? size() | |||
: jmin (size(), start + numberToJoin); | |||
if (start < 0) | |||
start = 0; | |||
if (start >= last) | |||
return String(); | |||
if (start == last - 1) | |||
return strings.getReference (start); | |||
const size_t separatorBytes = separator.text.sizeInBytes() - sizeof (String::CharPointerType::CharType); | |||
size_t bytesNeeded = separatorBytes * (size_t) (last - start - 1); | |||
for (int i = start; i < last; ++i) | |||
bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); | |||
String result; | |||
result.preallocateBytes (bytesNeeded); | |||
String::CharPointerType dest (result.getCharPointer()); | |||
while (start < last) | |||
{ | |||
const String& s = strings.getReference (start); | |||
if (! s.isEmpty()) | |||
dest.writeAll (s.getCharPointer()); | |||
if (++start < last && separatorBytes > 0) | |||
dest.writeAll (separator.text); | |||
} | |||
dest.writeNull(); | |||
return result; | |||
} | |||
int StringArray::addTokens (StringRef text, const bool preserveQuotedStrings) | |||
{ | |||
return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); | |||
} | |||
int StringArray::addTokens (StringRef text, StringRef breakCharacters, StringRef quoteCharacters) | |||
{ | |||
int num = 0; | |||
if (text.isNotEmpty()) | |||
{ | |||
for (String::CharPointerType t (text.text);;) | |||
{ | |||
String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t, | |||
breakCharacters.text, | |||
quoteCharacters.text)); | |||
strings.add (String (t, tokenEnd)); | |||
++num; | |||
if (tokenEnd.isEmpty()) | |||
break; | |||
t = ++tokenEnd; | |||
} | |||
} | |||
return num; | |||
} | |||
int StringArray::addLines (StringRef sourceText) | |||
{ | |||
int numLines = 0; | |||
String::CharPointerType text (sourceText.text); | |||
bool finished = text.isEmpty(); | |||
while (! finished) | |||
{ | |||
for (String::CharPointerType startOfLine (text);;) | |||
{ | |||
const String::CharPointerType endOfLine (text); | |||
switch (text.getAndAdvance()) | |||
{ | |||
case 0: finished = true; break; | |||
case '\n': break; | |||
case '\r': if (*text == '\n') ++text; break; | |||
default: continue; | |||
} | |||
strings.add (String (startOfLine, endOfLine)); | |||
++numLines; | |||
break; | |||
} | |||
} | |||
return numLines; | |||
} | |||
StringArray StringArray::fromTokens (StringRef stringToTokenise, bool preserveQuotedStrings) | |||
{ | |||
StringArray s; | |||
s.addTokens (stringToTokenise, preserveQuotedStrings); | |||
return s; | |||
} | |||
StringArray StringArray::fromTokens (StringRef stringToTokenise, | |||
StringRef breakCharacters, | |||
StringRef quoteCharacters) | |||
{ | |||
StringArray s; | |||
s.addTokens (stringToTokenise, breakCharacters, quoteCharacters); | |||
return s; | |||
} | |||
StringArray StringArray::fromLines (StringRef stringToBreakUp) | |||
{ | |||
StringArray s; | |||
s.addLines (stringToBreakUp); | |||
return s; | |||
} | |||
//============================================================================== | |||
void StringArray::removeDuplicates (const bool ignoreCase) | |||
{ | |||
for (int i = 0; i < size() - 1; ++i) | |||
{ | |||
const String s (strings.getReference(i)); | |||
for (int nextIndex = i + 1;;) | |||
{ | |||
nextIndex = indexOf (s, ignoreCase, nextIndex); | |||
if (nextIndex < 0) | |||
break; | |||
strings.remove (nextIndex); | |||
} | |||
} | |||
} | |||
void StringArray::appendNumbersToDuplicates (const bool ignoreCase, | |||
const bool appendNumberToFirstInstance, | |||
CharPointer_UTF8 preNumberString, | |||
CharPointer_UTF8 postNumberString) | |||
{ | |||
CharPointer_UTF8 defaultPre (" ("), defaultPost (")"); | |||
if (preNumberString.getAddress() == nullptr) | |||
preNumberString = defaultPre; | |||
if (postNumberString.getAddress() == nullptr) | |||
postNumberString = defaultPost; | |||
for (int i = 0; i < size() - 1; ++i) | |||
{ | |||
String& s = strings.getReference(i); | |||
int nextIndex = indexOf (s, ignoreCase, i + 1); | |||
if (nextIndex >= 0) | |||
{ | |||
const String original (s); | |||
int number = 0; | |||
if (appendNumberToFirstInstance) | |||
s = original + String (preNumberString) + String (++number) + String (postNumberString); | |||
else | |||
++number; | |||
while (nextIndex >= 0) | |||
{ | |||
set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString)); | |||
nextIndex = indexOf (original, ignoreCase, nextIndex + 1); | |||
} | |||
} | |||
} | |||
} | |||
void StringArray::ensureStorageAllocated (int minNumElements) | |||
{ | |||
strings.ensureStorageAllocated (minNumElements); | |||
} | |||
void StringArray::minimiseStorageOverheads() | |||
{ | |||
strings.minimiseStorageOverheads(); | |||
} |
@@ -0,0 +1,441 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_STRINGARRAY_H_INCLUDED | |||
#define JUCE_STRINGARRAY_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A special array for holding a list of strings. | |||
@see String, StringPairArray | |||
*/ | |||
class JUCE_API StringArray | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an empty string array */ | |||
StringArray() noexcept; | |||
/** Creates a copy of another string array */ | |||
StringArray (const StringArray&); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
StringArray (StringArray&&) noexcept; | |||
#endif | |||
/** Creates an array containing a single string. */ | |||
explicit StringArray (const String& firstValue); | |||
/** Creates an array from a raw array of strings. | |||
@param strings an array of strings to add | |||
@param numberOfStrings how many items there are in the array | |||
*/ | |||
StringArray (const String* strings, int numberOfStrings); | |||
/** Creates a copy of an array of string literals. | |||
@param strings an array of strings to add. Null pointers in the array will be | |||
treated as empty strings | |||
@param numberOfStrings how many items there are in the array | |||
*/ | |||
StringArray (const char* const* strings, int numberOfStrings); | |||
/** Creates a copy of a null-terminated array of string literals. | |||
Each item from the array passed-in is added, until it encounters a null pointer, | |||
at which point it stops. | |||
*/ | |||
explicit StringArray (const char* const* strings); | |||
/** Creates a copy of a null-terminated array of string literals. | |||
Each item from the array passed-in is added, until it encounters a null pointer, | |||
at which point it stops. | |||
*/ | |||
explicit StringArray (const wchar_t* const* strings); | |||
/** Creates a copy of an array of string literals. | |||
@param strings an array of strings to add. Null pointers in the array will be | |||
treated as empty strings | |||
@param numberOfStrings how many items there are in the array | |||
*/ | |||
StringArray (const wchar_t* const* strings, int numberOfStrings); | |||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
StringArray (const std::initializer_list<const char*>& strings); | |||
#endif | |||
/** Destructor. */ | |||
~StringArray(); | |||
/** Copies the contents of another string array into this one */ | |||
StringArray& operator= (const StringArray&); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
StringArray& operator= (StringArray&&) noexcept; | |||
#endif | |||
/** Swaps the contents of this and another StringArray. */ | |||
void swapWith (StringArray&) noexcept; | |||
//============================================================================== | |||
/** Compares two arrays. | |||
Comparisons are case-sensitive. | |||
@returns true only if the other array contains exactly the same strings in the same order | |||
*/ | |||
bool operator== (const StringArray&) const noexcept; | |||
/** Compares two arrays. | |||
Comparisons are case-sensitive. | |||
@returns false if the other array contains exactly the same strings in the same order | |||
*/ | |||
bool operator!= (const StringArray&) const noexcept; | |||
//============================================================================== | |||
/** Returns the number of strings in the array */ | |||
inline int size() const noexcept { return strings.size(); } | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept { return size() == 0; } | |||
/** Returns one of the strings from the array. | |||
If the index is out-of-range, an empty string is returned. | |||
Obviously the reference returned shouldn't be stored for later use, as the | |||
string it refers to may disappear when the array changes. | |||
*/ | |||
const String& operator[] (int index) const noexcept; | |||
/** Returns a reference to one of the strings in the array. | |||
This lets you modify a string in-place in the array, but you must be sure that | |||
the index is in-range. | |||
*/ | |||
String& getReference (int index) noexcept; | |||
/** Returns a pointer to the first String in the array. | |||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||
*/ | |||
inline String* begin() const noexcept { return strings.begin(); } | |||
/** Returns a pointer to the String which follows the last element in the array. | |||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||
*/ | |||
inline String* end() const noexcept { return strings.end(); } | |||
/** Searches for a string in the array. | |||
The comparison will be case-insensitive if the ignoreCase parameter is true. | |||
@returns true if the string is found inside the array | |||
*/ | |||
bool contains (StringRef stringToLookFor, | |||
bool ignoreCase = false) const; | |||
/** Searches for a string in the array. | |||
The comparison will be case-insensitive if the ignoreCase parameter is true. | |||
@param stringToLookFor the string to try to find | |||
@param ignoreCase whether the comparison should be case-insensitive | |||
@param startIndex the first index to start searching from | |||
@returns the index of the first occurrence of the string in this array, | |||
or -1 if it isn't found. | |||
*/ | |||
int indexOf (StringRef stringToLookFor, | |||
bool ignoreCase = false, | |||
int startIndex = 0) const; | |||
//============================================================================== | |||
/** Appends a string at the end of the array. */ | |||
void add (const String& stringToAdd); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
/** Appends a string at the end of the array. */ | |||
void add (String&& stringToAdd); | |||
#endif | |||
/** Inserts a string into the array. | |||
This will insert a string into the array at the given index, moving | |||
up the other elements to make room for it. | |||
If the index is less than zero or greater than the size of the array, | |||
the new string will be added to the end of the array. | |||
*/ | |||
void insert (int index, const String& stringToAdd); | |||
/** Adds a string to the array as long as it's not already in there. | |||
The search can optionally be case-insensitive. | |||
@return true if the string has been added, false otherwise. | |||
*/ | |||
bool addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false); | |||
/** Replaces one of the strings in the array with another one. | |||
If the index is higher than the array's size, the new string will be | |||
added to the end of the array; if it's less than zero nothing happens. | |||
*/ | |||
void set (int index, const String& newString); | |||
/** Appends some strings from another array to the end of this one. | |||
@param other the array to add | |||
@param startIndex the first element of the other array to add | |||
@param numElementsToAdd the maximum number of elements to add (if this is | |||
less than zero, they are all added) | |||
*/ | |||
void addArray (const StringArray& other, | |||
int startIndex = 0, | |||
int numElementsToAdd = -1); | |||
/** Merges the strings from another array into this one. | |||
This will not add a string that already exists. | |||
@param other the array to add | |||
@param ignoreCase ignore case when merging | |||
*/ | |||
void mergeArray (const StringArray& other, | |||
bool ignoreCase = false); | |||
/** Breaks up a string into tokens and adds them to this array. | |||
This will tokenise the given string using whitespace characters as the | |||
token delimiters, and will add these tokens to the end of the array. | |||
@returns the number of tokens added | |||
@see fromTokens | |||
*/ | |||
int addTokens (StringRef stringToTokenise, bool preserveQuotedStrings); | |||
/** Breaks up a string into tokens and adds them to this array. | |||
This will tokenise the given string (using the string passed in to define the | |||
token delimiters), and will add these tokens to the end of the array. | |||
@param stringToTokenise the string to tokenise | |||
@param breakCharacters a string of characters, any of which will be considered | |||
to be a token delimiter. | |||
@param quoteCharacters if this string isn't empty, it defines a set of characters | |||
which are treated as quotes. Any text occurring | |||
between quotes is not broken up into tokens. | |||
@returns the number of tokens added | |||
@see fromTokens | |||
*/ | |||
int addTokens (StringRef stringToTokenise, | |||
StringRef breakCharacters, | |||
StringRef quoteCharacters); | |||
/** Breaks up a string into lines and adds them to this array. | |||
This breaks a string down into lines separated by \\n or \\r\\n, and adds each line | |||
to the array. Line-break characters are omitted from the strings that are added to | |||
the array. | |||
*/ | |||
int addLines (StringRef stringToBreakUp); | |||
/** Returns an array containing the tokens in a given string. | |||
This will tokenise the given string using whitespace characters as the | |||
token delimiters, and return the parsed tokens as an array. | |||
@see addTokens | |||
*/ | |||
static StringArray fromTokens (StringRef stringToTokenise, | |||
bool preserveQuotedStrings); | |||
/** Returns an array containing the tokens in a given string. | |||
This will tokenise the given string using the breakCharacters string to define | |||
the token delimiters, and will return the parsed tokens as an array. | |||
@param stringToTokenise the string to tokenise | |||
@param breakCharacters a string of characters, any of which will be considered | |||
to be a token delimiter. | |||
@param quoteCharacters if this string isn't empty, it defines a set of characters | |||
which are treated as quotes. Any text occurring | |||
between quotes is not broken up into tokens. | |||
@see addTokens | |||
*/ | |||
static StringArray fromTokens (StringRef stringToTokenise, | |||
StringRef breakCharacters, | |||
StringRef quoteCharacters); | |||
/** Returns an array containing the lines in a given string. | |||
This breaks a string down into lines separated by \\n or \\r\\n, and returns an | |||
array containing these lines. Line-break characters are omitted from the strings that | |||
are added to the array. | |||
*/ | |||
static StringArray fromLines (StringRef stringToBreakUp); | |||
//============================================================================== | |||
/** Removes all elements from the array. */ | |||
void clear(); | |||
/** Removes all elements from the array without freeing the array's allocated storage. | |||
@see clear | |||
*/ | |||
void clearQuick(); | |||
/** Removes a string from the array. | |||
If the index is out-of-range, no action will be taken. | |||
*/ | |||
void remove (int index); | |||
/** Finds a string in the array and removes it. | |||
This will remove all occurrences of the given string from the array. | |||
The comparison may be case-insensitive depending on the ignoreCase parameter. | |||
*/ | |||
void removeString (StringRef stringToRemove, | |||
bool ignoreCase = false); | |||
/** Removes a range of elements from the array. | |||
This will remove a set of elements, starting from the given index, | |||
and move subsequent elements down to close the gap. | |||
If the range extends beyond the bounds of the array, it will | |||
be safely clipped to the size of the array. | |||
@param startIndex the index of the first element to remove | |||
@param numberToRemove how many elements should be removed | |||
*/ | |||
void removeRange (int startIndex, int numberToRemove); | |||
/** Removes any duplicated elements from the array. | |||
If any string appears in the array more than once, only the first occurrence of | |||
it will be retained. | |||
@param ignoreCase whether to use a case-insensitive comparison | |||
*/ | |||
void removeDuplicates (bool ignoreCase); | |||
/** Removes empty strings from the array. | |||
@param removeWhitespaceStrings if true, strings that only contain whitespace | |||
characters will also be removed | |||
*/ | |||
void removeEmptyStrings (bool removeWhitespaceStrings = true); | |||
/** Moves one of the strings to a different position. | |||
This will move the string to a specified index, shuffling along | |||
any intervening elements as required. | |||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling | |||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||
@param currentIndex the index of the value to be moved. If this isn't a | |||
valid index, then nothing will be done | |||
@param newIndex the index at which you'd like this value to end up. If this | |||
is less than zero, the value will be moved to the end | |||
of the array | |||
*/ | |||
void move (int currentIndex, int newIndex) noexcept; | |||
/** Deletes any whitespace characters from the starts and ends of all the strings. */ | |||
void trim(); | |||
/** Adds numbers to the strings in the array, to make each string unique. | |||
This will add numbers to the ends of groups of similar strings. | |||
e.g. if there are two "moose" strings, they will become "moose (1)" and "moose (2)" | |||
@param ignoreCaseWhenComparing whether the comparison used is case-insensitive | |||
@param appendNumberToFirstInstance whether the first of a group of similar strings | |||
also has a number appended to it. | |||
@param preNumberString when adding a number, this string is added before the number. | |||
If you pass nullptr, a default string will be used, which adds | |||
brackets around the number. | |||
@param postNumberString this string is appended after any numbers that are added. | |||
If you pass nullptr, a default string will be used, which adds | |||
brackets around the number. | |||
*/ | |||
void appendNumbersToDuplicates (bool ignoreCaseWhenComparing, | |||
bool appendNumberToFirstInstance, | |||
CharPointer_UTF8 preNumberString = CharPointer_UTF8 (nullptr), | |||
CharPointer_UTF8 postNumberString = CharPointer_UTF8 (nullptr)); | |||
//============================================================================== | |||
/** Joins the strings in the array together into one string. | |||
This will join a range of elements from the array into a string, separating | |||
them with a given string. | |||
e.g. joinIntoString (",") will turn an array of "a" "b" and "c" into "a,b,c". | |||
@param separatorString the string to insert between all the strings | |||
@param startIndex the first element to join | |||
@param numberOfElements how many elements to join together. If this is less | |||
than zero, all available elements will be used. | |||
*/ | |||
String joinIntoString (StringRef separatorString, | |||
int startIndex = 0, | |||
int numberOfElements = -1) const; | |||
//============================================================================== | |||
/** Sorts the array into alphabetical order. | |||
@param ignoreCase if true, the comparisons used will be case-sensitive. | |||
*/ | |||
void sort (bool ignoreCase); | |||
/** Sorts the array using extra language-aware rules to do a better job of comparing | |||
words containing spaces and numbers. | |||
@see String::compareNatural() | |||
*/ | |||
void sortNatural(); | |||
//============================================================================== | |||
/** Increases the array's internal storage to hold a minimum number of elements. | |||
Calling this before adding a large known number of elements means that | |||
the array won't have to keep dynamically resizing itself as the elements | |||
are added, and it'll therefore be more efficient. | |||
*/ | |||
void ensureStorageAllocated (int minNumElements); | |||
/** Reduces the amount of storage being used by the array. | |||
Arrays typically allocate slightly more storage than they need, and after | |||
removing elements, they may have quite a lot of unused space allocated. | |||
This method will reduce the amount of allocated storage to a minimum. | |||
*/ | |||
void minimiseStorageOverheads(); | |||
/** This is the array holding the actual strings. This is public to allow direct access | |||
to array methods that may not already be provided by the StringArray class. | |||
*/ | |||
Array<String> strings; | |||
private: | |||
JUCE_LEAK_DETECTOR (StringArray) | |||
}; | |||
#endif // JUCE_STRINGARRAY_H_INCLUDED |
@@ -0,0 +1,168 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
static const int minNumberOfStringsForGarbageCollection = 300; | |||
static const uint32 garbageCollectionInterval = 30000; | |||
StringPool::StringPool() noexcept : lastGarbageCollectionTime (0) {} | |||
StringPool::~StringPool() {} | |||
struct StartEndString | |||
{ | |||
StartEndString (String::CharPointerType s, String::CharPointerType e) noexcept : start (s), end (e) {} | |||
operator String() const { return String (start, end); } | |||
String::CharPointerType start, end; | |||
}; | |||
static int compareStrings (const String& s1, const String& s2) noexcept { return s1.compare (s2); } | |||
static int compareStrings (CharPointer_UTF8 s1, const String& s2) noexcept { return s1.compare (s2.getCharPointer()); } | |||
static int compareStrings (const StartEndString& string1, const String& string2) noexcept | |||
{ | |||
String::CharPointerType s1 (string1.start), s2 (string2.getCharPointer()); | |||
for (;;) | |||
{ | |||
const int c1 = s1 < string1.end ? (int) s1.getAndAdvance() : 0; | |||
const int c2 = (int) s2.getAndAdvance(); | |||
const int diff = c1 - c2; | |||
if (diff != 0) return diff < 0 ? -1 : 1; | |||
if (c1 == 0) break; | |||
} | |||
return 0; | |||
} | |||
template <typename NewStringType> | |||
static String addPooledString (Array<String>& strings, const NewStringType& newString) | |||
{ | |||
int start = 0; | |||
int end = strings.size(); | |||
while (start < end) | |||
{ | |||
const String& startString = strings.getReference (start); | |||
const int startComp = compareStrings (newString, startString); | |||
if (startComp == 0) | |||
return startString; | |||
const int halfway = (start + end) / 2; | |||
if (halfway == start) | |||
{ | |||
if (startComp > 0) | |||
++start; | |||
break; | |||
} | |||
const String& halfwayString = strings.getReference (halfway); | |||
const int halfwayComp = compareStrings (newString, halfwayString); | |||
if (halfwayComp == 0) | |||
return halfwayString; | |||
if (halfwayComp > 0) | |||
start = halfway; | |||
else | |||
end = halfway; | |||
} | |||
strings.insert (start, newString); | |||
return strings.getReference (start); | |||
} | |||
String StringPool::getPooledString (const char* const newString) | |||
{ | |||
if (newString == nullptr || *newString == 0) | |||
return String(); | |||
const ScopedLock sl (lock); | |||
garbageCollectIfNeeded(); | |||
return addPooledString (strings, CharPointer_UTF8 (newString)); | |||
} | |||
String StringPool::getPooledString (String::CharPointerType start, String::CharPointerType end) | |||
{ | |||
if (start.isEmpty() || start == end) | |||
return String(); | |||
const ScopedLock sl (lock); | |||
garbageCollectIfNeeded(); | |||
return addPooledString (strings, StartEndString (start, end)); | |||
} | |||
String StringPool::getPooledString (StringRef newString) | |||
{ | |||
if (newString.isEmpty()) | |||
return String(); | |||
const ScopedLock sl (lock); | |||
garbageCollectIfNeeded(); | |||
return addPooledString (strings, newString.text); | |||
} | |||
String StringPool::getPooledString (const String& newString) | |||
{ | |||
if (newString.isEmpty()) | |||
return String(); | |||
const ScopedLock sl (lock); | |||
garbageCollectIfNeeded(); | |||
return addPooledString (strings, newString); | |||
} | |||
void StringPool::garbageCollectIfNeeded() | |||
{ | |||
if (strings.size() > minNumberOfStringsForGarbageCollection | |||
&& Time::getApproximateMillisecondCounter() > lastGarbageCollectionTime + garbageCollectionInterval) | |||
garbageCollect(); | |||
} | |||
void StringPool::garbageCollect() | |||
{ | |||
const ScopedLock sl (lock); | |||
for (int i = strings.size(); --i >= 0;) | |||
if (strings.getReference(i).getReferenceCount() == 1) | |||
strings.remove (i); | |||
lastGarbageCollectionTime = Time::getApproximateMillisecondCounter(); | |||
} | |||
StringPool& StringPool::getGlobalPool() noexcept | |||
{ | |||
static StringPool pool; | |||
return pool; | |||
} |
@@ -0,0 +1,98 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_STRINGPOOL_H_INCLUDED | |||
#define JUCE_STRINGPOOL_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A StringPool holds a set of shared strings, which reduces storage overheads and improves | |||
comparison speed when dealing with many duplicate strings. | |||
When you add a string to a pool using getPooledString, it'll return a character | |||
array containing the same string. This array is owned by the pool, and the same array | |||
is returned every time a matching string is asked for. This means that it's trivial to | |||
compare two pooled strings for equality, as you can simply compare their pointers. It | |||
also cuts down on storage if you're using many copies of the same string. | |||
*/ | |||
class JUCE_API StringPool | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an empty pool. */ | |||
StringPool() noexcept; | |||
/** Destructor */ | |||
~StringPool(); | |||
//============================================================================== | |||
/** Returns a pointer to a shared copy of the string that is passed in. | |||
The pool will always return the same String object when asked for a string that matches it. | |||
*/ | |||
String getPooledString (const String& original); | |||
/** Returns a pointer to a copy of the string that is passed in. | |||
The pool will always return the same String object when asked for a string that matches it. | |||
*/ | |||
String getPooledString (const char* original); | |||
/** Returns a pointer to a shared copy of the string that is passed in. | |||
The pool will always return the same String object when asked for a string that matches it. | |||
*/ | |||
String getPooledString (StringRef original); | |||
/** Returns a pointer to a copy of the string that is passed in. | |||
The pool will always return the same String object when asked for a string that matches it. | |||
*/ | |||
String getPooledString (String::CharPointerType start, String::CharPointerType end); | |||
//============================================================================== | |||
/** Scans the pool, and removes any strings that are unreferenced. | |||
You don't generally need to call this - it'll be called automatically when the pool grows | |||
large enough to warrant it. | |||
*/ | |||
void garbageCollect(); | |||
/** Returns a shared global pool which is used for things like Identifiers, XML parsing. */ | |||
static StringPool& getGlobalPool() noexcept; | |||
private: | |||
Array<String> strings; | |||
CarlaRecursiveMutex lock; | |||
uint32 lastGarbageCollectionTime; | |||
void garbageCollectIfNeeded(); | |||
JUCE_DECLARE_NON_COPYABLE (StringPool) | |||
}; | |||
#endif // JUCE_STRINGPOOL_H_INCLUDED |
@@ -0,0 +1,895 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
XmlDocument::XmlDocument (const String& documentText) | |||
: originalText (documentText), | |||
input (nullptr), | |||
outOfData (false), | |||
errorOccurred (false), | |||
needToLoadDTD (false), | |||
ignoreEmptyTextElements (true) | |||
{ | |||
} | |||
XmlDocument::XmlDocument (const File& file) | |||
: input (nullptr), | |||
outOfData (false), | |||
errorOccurred (false), | |||
needToLoadDTD (false), | |||
ignoreEmptyTextElements (true), | |||
inputSource (new FileInputSource (file)) | |||
{ | |||
} | |||
XmlDocument::~XmlDocument() | |||
{ | |||
} | |||
XmlElement* XmlDocument::parse (const File& file) | |||
{ | |||
XmlDocument doc (file); | |||
return doc.getDocumentElement(); | |||
} | |||
XmlElement* XmlDocument::parse (const String& xmlData) | |||
{ | |||
XmlDocument doc (xmlData); | |||
return doc.getDocumentElement(); | |||
} | |||
void XmlDocument::setInputSource (InputSource* const newSource) noexcept | |||
{ | |||
inputSource = newSource; | |||
} | |||
void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept | |||
{ | |||
ignoreEmptyTextElements = shouldBeIgnored; | |||
} | |||
namespace XmlIdentifierChars | |||
{ | |||
static bool isIdentifierCharSlow (const juce_wchar c) noexcept | |||
{ | |||
return CharacterFunctions::isLetterOrDigit (c) | |||
|| c == '_' || c == '-' || c == ':' || c == '.'; | |||
} | |||
static bool isIdentifierChar (const juce_wchar c) noexcept | |||
{ | |||
static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 }; | |||
return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0) | |||
: isIdentifierCharSlow (c); | |||
} | |||
/*static void generateIdentifierCharConstants() | |||
{ | |||
uint32 n[8] = { 0 }; | |||
for (int i = 0; i < 256; ++i) | |||
if (isIdentifierCharSlow (i)) | |||
n[i >> 5] |= (1 << (i & 31)); | |||
String s; | |||
for (int i = 0; i < 8; ++i) | |||
s << "0x" << String::toHexString ((int) n[i]) << ", "; | |||
DBG (s); | |||
}*/ | |||
static String::CharPointerType findEndOfToken (String::CharPointerType p) | |||
{ | |||
while (isIdentifierChar (*p)) | |||
++p; | |||
return p; | |||
} | |||
} | |||
XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) | |||
{ | |||
if (originalText.isEmpty() && inputSource != nullptr) | |||
{ | |||
ScopedPointer<InputStream> in (inputSource->createInputStream()); | |||
if (in != nullptr) | |||
{ | |||
MemoryOutputStream data; | |||
data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); | |||
#if JUCE_STRING_UTF_TYPE == 8 | |||
if (data.getDataSize() > 2) | |||
{ | |||
data.writeByte (0); | |||
const char* text = static_cast<const char*> (data.getData()); | |||
if (CharPointer_UTF16::isByteOrderMarkBigEndian (text) | |||
|| CharPointer_UTF16::isByteOrderMarkLittleEndian (text)) | |||
{ | |||
originalText = data.toString(); | |||
} | |||
else | |||
{ | |||
if (CharPointer_UTF8::isByteOrderMark (text)) | |||
text += 3; | |||
// parse the input buffer directly to avoid copying it all to a string.. | |||
return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement); | |||
} | |||
} | |||
#else | |||
originalText = data.toString(); | |||
#endif | |||
} | |||
} | |||
return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement); | |||
} | |||
const String& XmlDocument::getLastParseError() const noexcept | |||
{ | |||
return lastError; | |||
} | |||
void XmlDocument::setLastError (const String& desc, const bool carryOn) | |||
{ | |||
lastError = desc; | |||
errorOccurred = ! carryOn; | |||
} | |||
String XmlDocument::getFileContents (const String& filename) const | |||
{ | |||
if (inputSource != nullptr) | |||
{ | |||
const ScopedPointer<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted())); | |||
if (in != nullptr) | |||
return in->readEntireStreamAsString(); | |||
} | |||
return String(); | |||
} | |||
juce_wchar XmlDocument::readNextChar() noexcept | |||
{ | |||
const juce_wchar c = input.getAndAdvance(); | |||
if (c == 0) | |||
{ | |||
outOfData = true; | |||
--input; | |||
} | |||
return c; | |||
} | |||
XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToParse, | |||
const bool onlyReadOuterDocumentElement) | |||
{ | |||
input = textToParse; | |||
errorOccurred = false; | |||
outOfData = false; | |||
needToLoadDTD = true; | |||
if (textToParse.isEmpty()) | |||
{ | |||
lastError = "not enough input"; | |||
} | |||
else if (! parseHeader()) | |||
{ | |||
lastError = "malformed header"; | |||
} | |||
else if (! parseDTD()) | |||
{ | |||
lastError = "malformed DTD"; | |||
} | |||
else | |||
{ | |||
lastError.clear(); | |||
ScopedPointer<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement)); | |||
if (! errorOccurred) | |||
return result.release(); | |||
} | |||
return nullptr; | |||
} | |||
bool XmlDocument::parseHeader() | |||
{ | |||
skipNextWhiteSpace(); | |||
if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<?xml"), 5) == 0) | |||
{ | |||
const String::CharPointerType headerEnd (CharacterFunctions::find (input, CharPointer_UTF8 ("?>"))); | |||
if (headerEnd.isEmpty()) | |||
return false; | |||
#if JUCE_DEBUG | |||
const String encoding (String (input, headerEnd) | |||
.fromFirstOccurrenceOf ("encoding", false, true) | |||
.fromFirstOccurrenceOf ("=", false, false) | |||
.fromFirstOccurrenceOf ("\"", false, false) | |||
.upToFirstOccurrenceOf ("\"", false, false).trim()); | |||
/* If you load an XML document with a non-UTF encoding type, it may have been | |||
loaded wrongly.. Since all the files are read via the normal juce file streams, | |||
they're treated as UTF-8, so by the time it gets to the parser, the encoding will | |||
have been lost. Best plan is to stick to utf-8 or if you have specific files to | |||
read, use your own code to convert them to a unicode String, and pass that to the | |||
XML parser. | |||
*/ | |||
jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-")); | |||
#endif | |||
input = headerEnd + 2; | |||
skipNextWhiteSpace(); | |||
} | |||
return true; | |||
} | |||
bool XmlDocument::parseDTD() | |||
{ | |||
if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<!DOCTYPE"), 9) == 0) | |||
{ | |||
input += 9; | |||
const String::CharPointerType dtdStart (input); | |||
for (int n = 1; n > 0;) | |||
{ | |||
const juce_wchar c = readNextChar(); | |||
if (outOfData) | |||
return false; | |||
if (c == '<') | |||
++n; | |||
else if (c == '>') | |||
--n; | |||
} | |||
dtdText = String (dtdStart, input - 1).trim(); | |||
} | |||
return true; | |||
} | |||
void XmlDocument::skipNextWhiteSpace() | |||
{ | |||
for (;;) | |||
{ | |||
input = input.findEndOfWhitespace(); | |||
if (input.isEmpty()) | |||
{ | |||
outOfData = true; | |||
break; | |||
} | |||
if (*input == '<') | |||
{ | |||
if (input[1] == '!' | |||
&& input[2] == '-' | |||
&& input[3] == '-') | |||
{ | |||
input += 4; | |||
const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); | |||
if (closeComment < 0) | |||
{ | |||
outOfData = true; | |||
break; | |||
} | |||
input += closeComment + 3; | |||
continue; | |||
} | |||
if (input[1] == '?') | |||
{ | |||
input += 2; | |||
const int closeBracket = input.indexOf (CharPointer_UTF8 ("?>")); | |||
if (closeBracket < 0) | |||
{ | |||
outOfData = true; | |||
break; | |||
} | |||
input += closeBracket + 2; | |||
continue; | |||
} | |||
} | |||
break; | |||
} | |||
} | |||
void XmlDocument::readQuotedString (String& result) | |||
{ | |||
const juce_wchar quote = readNextChar(); | |||
while (! outOfData) | |||
{ | |||
const juce_wchar c = readNextChar(); | |||
if (c == quote) | |||
break; | |||
--input; | |||
if (c == '&') | |||
{ | |||
readEntity (result); | |||
} | |||
else | |||
{ | |||
const String::CharPointerType start (input); | |||
for (;;) | |||
{ | |||
const juce_wchar character = *input; | |||
if (character == quote) | |||
{ | |||
result.appendCharPointer (start, input); | |||
++input; | |||
return; | |||
} | |||
else if (character == '&') | |||
{ | |||
result.appendCharPointer (start, input); | |||
break; | |||
} | |||
else if (character == 0) | |||
{ | |||
setLastError ("unmatched quotes", false); | |||
outOfData = true; | |||
break; | |||
} | |||
++input; | |||
} | |||
} | |||
} | |||
} | |||
XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||
{ | |||
XmlElement* node = nullptr; | |||
skipNextWhiteSpace(); | |||
if (outOfData) | |||
return nullptr; | |||
if (*input == '<') | |||
{ | |||
++input; | |||
String::CharPointerType endOfToken (XmlIdentifierChars::findEndOfToken (input)); | |||
if (endOfToken == input) | |||
{ | |||
// no tag name - but allow for a gap after the '<' before giving an error | |||
skipNextWhiteSpace(); | |||
endOfToken = XmlIdentifierChars::findEndOfToken (input); | |||
if (endOfToken == input) | |||
{ | |||
setLastError ("tag name missing", false); | |||
return node; | |||
} | |||
} | |||
node = new XmlElement (input, endOfToken); | |||
input = endOfToken; | |||
LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes); | |||
// look for attributes | |||
for (;;) | |||
{ | |||
skipNextWhiteSpace(); | |||
const juce_wchar c = *input; | |||
// empty tag.. | |||
if (c == '/' && input[1] == '>') | |||
{ | |||
input += 2; | |||
break; | |||
} | |||
// parse the guts of the element.. | |||
if (c == '>') | |||
{ | |||
++input; | |||
if (alsoParseSubElements) | |||
readChildElements (*node); | |||
break; | |||
} | |||
// get an attribute.. | |||
if (XmlIdentifierChars::isIdentifierChar (c)) | |||
{ | |||
String::CharPointerType attNameEnd (XmlIdentifierChars::findEndOfToken (input)); | |||
if (attNameEnd != input) | |||
{ | |||
const String::CharPointerType attNameStart (input); | |||
input = attNameEnd; | |||
skipNextWhiteSpace(); | |||
if (readNextChar() == '=') | |||
{ | |||
skipNextWhiteSpace(); | |||
const juce_wchar nextChar = *input; | |||
if (nextChar == '"' || nextChar == '\'') | |||
{ | |||
XmlElement::XmlAttributeNode* const newAtt | |||
= new XmlElement::XmlAttributeNode (attNameStart, attNameEnd); | |||
readQuotedString (newAtt->value); | |||
attributeAppender.append (newAtt); | |||
continue; | |||
} | |||
} | |||
else | |||
{ | |||
setLastError ("expected '=' after attribute '" | |||
+ String (attNameStart, attNameEnd) + "'", false); | |||
return node; | |||
} | |||
} | |||
} | |||
else | |||
{ | |||
if (! outOfData) | |||
setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); | |||
} | |||
break; | |||
} | |||
} | |||
return node; | |||
} | |||
void XmlDocument::readChildElements (XmlElement& parent) | |||
{ | |||
LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement); | |||
for (;;) | |||
{ | |||
const String::CharPointerType preWhitespaceInput (input); | |||
skipNextWhiteSpace(); | |||
if (outOfData) | |||
{ | |||
setLastError ("unmatched tags", false); | |||
break; | |||
} | |||
if (*input == '<') | |||
{ | |||
const juce_wchar c1 = input[1]; | |||
if (c1 == '/') | |||
{ | |||
// our close tag.. | |||
const int closeTag = input.indexOf ((juce_wchar) '>'); | |||
if (closeTag >= 0) | |||
input += closeTag + 1; | |||
break; | |||
} | |||
if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_UTF8 ("[CDATA["), 7) == 0) | |||
{ | |||
input += 9; | |||
const String::CharPointerType inputStart (input); | |||
for (;;) | |||
{ | |||
const juce_wchar c0 = *input; | |||
if (c0 == 0) | |||
{ | |||
setLastError ("unterminated CDATA section", false); | |||
outOfData = true; | |||
break; | |||
} | |||
else if (c0 == ']' | |||
&& input[1] == ']' | |||
&& input[2] == '>') | |||
{ | |||
childAppender.append (XmlElement::createTextElement (String (inputStart, input))); | |||
input += 3; | |||
break; | |||
} | |||
++input; | |||
} | |||
} | |||
else | |||
{ | |||
// this is some other element, so parse and add it.. | |||
if (XmlElement* const n = readNextElement (true)) | |||
childAppender.append (n); | |||
else | |||
break; | |||
} | |||
} | |||
else // must be a character block | |||
{ | |||
input = preWhitespaceInput; // roll back to include the leading whitespace | |||
MemoryOutputStream textElementContent; | |||
bool contentShouldBeUsed = ! ignoreEmptyTextElements; | |||
for (;;) | |||
{ | |||
const juce_wchar c = *input; | |||
if (c == '<') | |||
{ | |||
if (input[1] == '!' && input[2] == '-' && input[3] == '-') | |||
{ | |||
input += 4; | |||
const int closeComment = input.indexOf (CharPointer_UTF8 ("-->")); | |||
if (closeComment < 0) | |||
{ | |||
setLastError ("unterminated comment", false); | |||
outOfData = true; | |||
return; | |||
} | |||
input += closeComment + 3; | |||
continue; | |||
} | |||
break; | |||
} | |||
if (c == 0) | |||
{ | |||
setLastError ("unmatched tags", false); | |||
outOfData = true; | |||
return; | |||
} | |||
if (c == '&') | |||
{ | |||
String entity; | |||
readEntity (entity); | |||
if (entity.startsWithChar ('<') && entity [1] != 0) | |||
{ | |||
const String::CharPointerType oldInput (input); | |||
const bool oldOutOfData = outOfData; | |||
input = entity.getCharPointer(); | |||
outOfData = false; | |||
while (XmlElement* n = readNextElement (true)) | |||
childAppender.append (n); | |||
input = oldInput; | |||
outOfData = oldOutOfData; | |||
} | |||
else | |||
{ | |||
textElementContent << entity; | |||
contentShouldBeUsed = contentShouldBeUsed || entity.containsNonWhitespaceChars(); | |||
} | |||
} | |||
else | |||
{ | |||
for (;; ++input) | |||
{ | |||
juce_wchar nextChar = *input; | |||
if (nextChar == '\r') | |||
{ | |||
nextChar = '\n'; | |||
if (input[1] == '\n') | |||
continue; | |||
} | |||
if (nextChar == '<' || nextChar == '&') | |||
break; | |||
if (nextChar == 0) | |||
{ | |||
setLastError ("unmatched tags", false); | |||
outOfData = true; | |||
return; | |||
} | |||
textElementContent.appendUTF8Char (nextChar); | |||
contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar); | |||
} | |||
} | |||
} | |||
if (contentShouldBeUsed) | |||
childAppender.append (XmlElement::createTextElement (textElementContent.toUTF8())); | |||
} | |||
} | |||
} | |||
void XmlDocument::readEntity (String& result) | |||
{ | |||
// skip over the ampersand | |||
++input; | |||
if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("amp;"), 4) == 0) | |||
{ | |||
input += 4; | |||
result += '&'; | |||
} | |||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("quot;"), 5) == 0) | |||
{ | |||
input += 5; | |||
result += '"'; | |||
} | |||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("apos;"), 5) == 0) | |||
{ | |||
input += 5; | |||
result += '\''; | |||
} | |||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("lt;"), 3) == 0) | |||
{ | |||
input += 3; | |||
result += '<'; | |||
} | |||
else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("gt;"), 3) == 0) | |||
{ | |||
input += 3; | |||
result += '>'; | |||
} | |||
else if (*input == '#') | |||
{ | |||
int charCode = 0; | |||
++input; | |||
if (*input == 'x' || *input == 'X') | |||
{ | |||
++input; | |||
int numChars = 0; | |||
while (input[0] != ';') | |||
{ | |||
const int hexValue = CharacterFunctions::getHexDigitValue (input[0]); | |||
if (hexValue < 0 || ++numChars > 8) | |||
{ | |||
setLastError ("illegal escape sequence", true); | |||
break; | |||
} | |||
charCode = (charCode << 4) | hexValue; | |||
++input; | |||
} | |||
++input; | |||
} | |||
else if (input[0] >= '0' && input[0] <= '9') | |||
{ | |||
int numChars = 0; | |||
while (input[0] != ';') | |||
{ | |||
if (++numChars > 12) | |||
{ | |||
setLastError ("illegal escape sequence", true); | |||
break; | |||
} | |||
charCode = charCode * 10 + ((int) input[0] - '0'); | |||
++input; | |||
} | |||
++input; | |||
} | |||
else | |||
{ | |||
setLastError ("illegal escape sequence", true); | |||
result += '&'; | |||
return; | |||
} | |||
#if 0 // FIXME | |||
result << (juce_wchar) charCode; | |||
#endif | |||
} | |||
else | |||
{ | |||
const String::CharPointerType entityNameStart (input); | |||
const int closingSemiColon = input.indexOf ((juce_wchar) ';'); | |||
if (closingSemiColon < 0) | |||
{ | |||
outOfData = true; | |||
result += '&'; | |||
} | |||
else | |||
{ | |||
input += closingSemiColon + 1; | |||
result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon)); | |||
} | |||
} | |||
} | |||
String XmlDocument::expandEntity (const String& ent) | |||
{ | |||
if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&'); | |||
if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"'); | |||
if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\''); | |||
if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<'); | |||
if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>'); | |||
if (ent[0] == '#') | |||
{ | |||
const juce_wchar char1 = ent[1]; | |||
if (char1 == 'x' || char1 == 'X') | |||
return String::charToString (static_cast<juce_wchar> (ent.substring (2).getHexValue32())); | |||
if (char1 >= '0' && char1 <= '9') | |||
return String::charToString (static_cast<juce_wchar> (ent.substring (1).getIntValue())); | |||
setLastError ("illegal escape sequence", false); | |||
return String::charToString ('&'); | |||
} | |||
return expandExternalEntity (ent); | |||
} | |||
String XmlDocument::expandExternalEntity (const String& entity) | |||
{ | |||
if (needToLoadDTD) | |||
{ | |||
if (dtdText.isNotEmpty()) | |||
{ | |||
dtdText = dtdText.trimCharactersAtEnd (">"); | |||
tokenisedDTD.addTokens (dtdText, true); | |||
if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") | |||
&& tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) | |||
{ | |||
const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); | |||
tokenisedDTD.clear(); | |||
tokenisedDTD.addTokens (getFileContents (fn), true); | |||
} | |||
else | |||
{ | |||
tokenisedDTD.clear(); | |||
const int openBracket = dtdText.indexOfChar ('['); | |||
if (openBracket > 0) | |||
{ | |||
const int closeBracket = dtdText.lastIndexOfChar (']'); | |||
if (closeBracket > openBracket) | |||
tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, | |||
closeBracket), true); | |||
} | |||
} | |||
for (int i = tokenisedDTD.size(); --i >= 0;) | |||
{ | |||
if (tokenisedDTD[i].startsWithChar ('%') | |||
&& tokenisedDTD[i].endsWithChar (';')) | |||
{ | |||
const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); | |||
StringArray newToks; | |||
newToks.addTokens (parsed, true); | |||
tokenisedDTD.remove (i); | |||
for (int j = newToks.size(); --j >= 0;) | |||
tokenisedDTD.insert (i, newToks[j]); | |||
} | |||
} | |||
} | |||
needToLoadDTD = false; | |||
} | |||
for (int i = 0; i < tokenisedDTD.size(); ++i) | |||
{ | |||
if (tokenisedDTD[i] == entity) | |||
{ | |||
if (tokenisedDTD[i - 1].equalsIgnoreCase ("<!entity")) | |||
{ | |||
String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">").trim().unquoted()); | |||
// check for sub-entities.. | |||
int ampersand = ent.indexOfChar ('&'); | |||
while (ampersand >= 0) | |||
{ | |||
const int semiColon = ent.indexOf (i + 1, ";"); | |||
if (semiColon < 0) | |||
{ | |||
setLastError ("entity without terminating semi-colon", false); | |||
break; | |||
} | |||
const String resolved (expandEntity (ent.substring (i + 1, semiColon))); | |||
ent = ent.substring (0, ampersand) | |||
+ resolved | |||
+ ent.substring (semiColon + 1); | |||
ampersand = ent.indexOfChar (semiColon + 1, '&'); | |||
} | |||
return ent; | |||
} | |||
} | |||
} | |||
setLastError ("unknown entity", true); | |||
return entity; | |||
} | |||
String XmlDocument::getParameterEntity (const String& entity) | |||
{ | |||
for (int i = 0; i < tokenisedDTD.size(); ++i) | |||
{ | |||
if (tokenisedDTD[i] == entity | |||
&& tokenisedDTD [i - 1] == "%" | |||
&& tokenisedDTD [i - 2].equalsIgnoreCase ("<!entity")) | |||
{ | |||
const String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">")); | |||
if (ent.equalsIgnoreCase ("system")) | |||
return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); | |||
return ent.trim().unquoted(); | |||
} | |||
} | |||
return entity; | |||
} |
@@ -0,0 +1,183 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_XMLDOCUMENT_H_INCLUDED | |||
#define JUCE_XMLDOCUMENT_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Parses a text-based XML document and creates an XmlElement object from it. | |||
The parser will parse DTDs to load external entities but won't | |||
check the document for validity against the DTD. | |||
e.g. | |||
@code | |||
XmlDocument myDocument (File ("myfile.xml")); | |||
ScopedPointer<XmlElement> mainElement (myDocument.getDocumentElement()); | |||
if (mainElement == nullptr) | |||
{ | |||
String error = myDocument.getLastParseError(); | |||
} | |||
else | |||
{ | |||
..use the element | |||
} | |||
@endcode | |||
Or you can use the static helper methods for quick parsing.. | |||
@code | |||
ScopedPointer<XmlElement> xml (XmlDocument::parse (myXmlFile)); | |||
if (xml != nullptr && xml->hasTagName ("foobar")) | |||
{ | |||
...etc | |||
@endcode | |||
@see XmlElement | |||
*/ | |||
class JUCE_API XmlDocument | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an XmlDocument from the xml text. | |||
The text doesn't actually get parsed until the getDocumentElement() method is called. | |||
*/ | |||
XmlDocument (const String& documentText); | |||
/** Creates an XmlDocument from a file. | |||
The text doesn't actually get parsed until the getDocumentElement() method is called. | |||
*/ | |||
XmlDocument (const File& file); | |||
/** Destructor. */ | |||
~XmlDocument(); | |||
//============================================================================== | |||
/** Creates an XmlElement object to represent the main document node. | |||
This method will do the actual parsing of the text, and if there's a | |||
parse error, it may returns nullptr (and you can find out the error using | |||
the getLastParseError() method). | |||
See also the parse() methods, which provide a shorthand way to quickly | |||
parse a file or string. | |||
@param onlyReadOuterDocumentElement if true, the parser will only read the | |||
first section of the file, and will only | |||
return the outer document element - this | |||
allows quick checking of large files to | |||
see if they contain the correct type of | |||
tag, without having to parse the entire file | |||
@returns a new XmlElement which the caller will need to delete, or null if | |||
there was an error. | |||
@see getLastParseError | |||
*/ | |||
XmlElement* getDocumentElement (bool onlyReadOuterDocumentElement = false); | |||
/** Returns the parsing error that occurred the last time getDocumentElement was called. | |||
@returns the error, or an empty string if there was no error. | |||
*/ | |||
const String& getLastParseError() const noexcept; | |||
/** Sets an input source object to use for parsing documents that reference external entities. | |||
If the document has been created from a file, this probably won't be needed, but | |||
if you're parsing some text and there might be a DTD that references external | |||
files, you may need to create a custom input source that can retrieve the | |||
other files it needs. | |||
The object that is passed-in will be deleted automatically when no longer needed. | |||
@see InputSource | |||
*/ | |||
void setInputSource (InputSource* newSource) noexcept; | |||
/** Sets a flag to change the treatment of empty text elements. | |||
If this is true (the default state), then any text elements that contain only | |||
whitespace characters will be ingored during parsing. If you need to catch | |||
whitespace-only text, then you should set this to false before calling the | |||
getDocumentElement() method. | |||
*/ | |||
void setEmptyTextElementsIgnored (bool shouldBeIgnored) noexcept; | |||
//============================================================================== | |||
/** A handy static method that parses a file. | |||
This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. | |||
@returns a new XmlElement which the caller will need to delete, or null if there was an error. | |||
*/ | |||
static XmlElement* parse (const File& file); | |||
/** A handy static method that parses some XML data. | |||
This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. | |||
@returns a new XmlElement which the caller will need to delete, or null if there was an error. | |||
*/ | |||
static XmlElement* parse (const String& xmlData); | |||
//============================================================================== | |||
private: | |||
String originalText; | |||
String::CharPointerType input; | |||
bool outOfData, errorOccurred; | |||
String lastError, dtdText; | |||
StringArray tokenisedDTD; | |||
bool needToLoadDTD, ignoreEmptyTextElements; | |||
ScopedPointer<InputSource> inputSource; | |||
XmlElement* parseDocumentElement (String::CharPointerType, bool outer); | |||
void setLastError (const String&, bool carryOn); | |||
bool parseHeader(); | |||
bool parseDTD(); | |||
void skipNextWhiteSpace(); | |||
juce_wchar readNextChar() noexcept; | |||
XmlElement* readNextElement (bool alsoParseSubElements); | |||
void readChildElements (XmlElement&); | |||
void readQuotedString (String&); | |||
void readEntity (String&); | |||
String getFileContents (const String&) const; | |||
String expandEntity (const String&); | |||
String expandExternalEntity (const String&); | |||
String getParameterEntity (const String&); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) | |||
}; | |||
#endif // JUCE_XMLDOCUMENT_H_INCLUDED |
@@ -0,0 +1,942 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
namespace | |||
{ | |||
inline bool isValidXmlNameStartCharacter (const juce_wchar character) noexcept | |||
{ | |||
return character == ':' | |||
|| character == '_' | |||
|| (character >= 'a' && character <= 'z') | |||
|| (character >= 'A' && character <= 'Z') | |||
|| (character >= 0xc0 && character <= 0xd6) | |||
|| (character >= 0xd8 && character <= 0xf6) | |||
|| (character >= 0xf8 && character <= 0x2ff) | |||
|| (character >= 0x370 && character <= 0x37d) | |||
|| (character >= 0x37f && character <= 0x1fff) | |||
|| (character >= 0x200c && character <= 0x200d) | |||
|| (character >= 0x2070 && character <= 0x218f) | |||
|| (character >= 0x2c00 && character <= 0x2fef) | |||
|| (character >= 0x3001 && character <= 0xd7ff) | |||
|| (character >= 0xf900 && character <= 0xfdcf) | |||
|| (character >= 0xfdf0 && character <= 0xfffd) | |||
|| (character >= 0x10000 && character <= 0xeffff); | |||
} | |||
inline bool isValidXmlNameBodyCharacter (const juce_wchar character) noexcept | |||
{ | |||
return isValidXmlNameStartCharacter (character) | |||
|| character == '-' | |||
|| character == '.' | |||
|| character == 0xb7 | |||
|| (character >= '0' && character <= '9') | |||
|| (character >= 0x300 && character <= 0x036f) | |||
|| (character >= 0x203f && character <= 0x2040); | |||
} | |||
} | |||
XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept | |||
: name (other.name), | |||
value (other.value) | |||
{ | |||
} | |||
XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept | |||
: name (n), value (v) | |||
{ | |||
jassert (isValidXmlName (name)); | |||
} | |||
XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd) | |||
: name (nameStart, nameEnd) | |||
{ | |||
jassert (isValidXmlName (name)); | |||
} | |||
//============================================================================== | |||
XmlElement::XmlElement (const String& tag) | |||
: tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
{ | |||
jassert (isValidXmlName (tagName)); | |||
} | |||
XmlElement::XmlElement (const char* tag) | |||
: tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
{ | |||
jassert (isValidXmlName (tagName)); | |||
} | |||
XmlElement::XmlElement (StringRef tag) | |||
: tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
{ | |||
jassert (isValidXmlName (tagName)); | |||
} | |||
XmlElement::XmlElement (const Identifier& tag) | |||
: tagName (tag.toString()) | |||
{ | |||
jassert (isValidXmlName (tagName)); | |||
} | |||
XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd) | |||
: tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd)) | |||
{ | |||
jassert (isValidXmlName (tagName)); | |||
} | |||
XmlElement::XmlElement (int /*dummy*/) noexcept | |||
{ | |||
} | |||
XmlElement::XmlElement (const XmlElement& other) | |||
: tagName (other.tagName) | |||
{ | |||
copyChildrenAndAttributesFrom (other); | |||
} | |||
XmlElement& XmlElement::operator= (const XmlElement& other) | |||
{ | |||
if (this != &other) | |||
{ | |||
removeAllAttributes(); | |||
deleteAllChildElements(); | |||
tagName = other.tagName; | |||
copyChildrenAndAttributesFrom (other); | |||
} | |||
return *this; | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
XmlElement::XmlElement (XmlElement&& other) noexcept | |||
: nextListItem (static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem)), | |||
firstChildElement (static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement)), | |||
attributes (static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes)), | |||
tagName (static_cast<String&&> (other.tagName)) | |||
{ | |||
} | |||
XmlElement& XmlElement::operator= (XmlElement&& other) noexcept | |||
{ | |||
jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||
removeAllAttributes(); | |||
deleteAllChildElements(); | |||
nextListItem = static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem); | |||
firstChildElement = static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement); | |||
attributes = static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes); | |||
tagName = static_cast<String&&> (other.tagName); | |||
return *this; | |||
} | |||
#endif | |||
void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) | |||
{ | |||
jassert (firstChildElement.get() == nullptr); | |||
firstChildElement.addCopyOfList (other.firstChildElement); | |||
jassert (attributes.get() == nullptr); | |||
attributes.addCopyOfList (other.attributes); | |||
} | |||
XmlElement::~XmlElement() noexcept | |||
{ | |||
firstChildElement.deleteAll(); | |||
attributes.deleteAll(); | |||
} | |||
//============================================================================== | |||
namespace XmlOutputFunctions | |||
{ | |||
#if 0 // (These functions are just used to generate the lookup table used below) | |||
bool isLegalXmlCharSlow (const juce_wchar character) noexcept | |||
{ | |||
if ((character >= 'a' && character <= 'z') | |||
|| (character >= 'A' && character <= 'Z') | |||
|| (character >= '0' && character <= '9')) | |||
return true; | |||
const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; | |||
do | |||
{ | |||
if (((juce_wchar) (uint8) *t) == character) | |||
return true; | |||
} | |||
while (*++t != 0); | |||
return false; | |||
} | |||
void generateLegalCharLookupTable() | |||
{ | |||
uint8 n[32] = { 0 }; | |||
for (int i = 0; i < 256; ++i) | |||
if (isLegalXmlCharSlow (i)) | |||
n[i >> 3] |= (1 << (i & 7)); | |||
String s; | |||
for (int i = 0; i < 32; ++i) | |||
s << (int) n[i] << ", "; | |||
DBG (s); | |||
} | |||
#endif | |||
static bool isLegalXmlChar (const uint32 c) noexcept | |||
{ | |||
static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, | |||
255, 255, 191, 254, 255, 255, 127 }; | |||
return c < sizeof (legalChars) * 8 | |||
&& (legalChars [c >> 3] & (1 << (c & 7))) != 0; | |||
} | |||
static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) | |||
{ | |||
String::CharPointerType t (text.getCharPointer()); | |||
for (;;) | |||
{ | |||
const uint32 character = (uint32) t.getAndAdvance(); | |||
if (character == 0) | |||
break; | |||
if (isLegalXmlChar (character)) | |||
{ | |||
outputStream << (char) character; | |||
} | |||
else | |||
{ | |||
switch (character) | |||
{ | |||
case '&': outputStream << "&"; break; | |||
case '"': outputStream << """; break; | |||
case '>': outputStream << ">"; break; | |||
case '<': outputStream << "<"; break; | |||
case '\n': | |||
case '\r': | |||
if (! changeNewLines) | |||
{ | |||
outputStream << (char) character; | |||
break; | |||
} | |||
// Note: deliberate fall-through here! | |||
default: | |||
outputStream << "&#" << ((int) character) << ';'; | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
static void writeSpaces (OutputStream& out, const size_t numSpaces) | |||
{ | |||
out.writeRepeatedByte (' ', numSpaces); | |||
} | |||
} | |||
void XmlElement::writeElementAsText (OutputStream& outputStream, | |||
const int indentationLevel, | |||
const int lineWrapLength) const | |||
{ | |||
using namespace XmlOutputFunctions; | |||
if (indentationLevel >= 0) | |||
writeSpaces (outputStream, (size_t) indentationLevel); | |||
if (! isTextElement()) | |||
{ | |||
outputStream.writeByte ('<'); | |||
outputStream << tagName; | |||
{ | |||
const size_t attIndent = (size_t) (indentationLevel + tagName.length() + 1); | |||
int lineLen = 0; | |||
for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||
{ | |||
if (lineLen > lineWrapLength && indentationLevel >= 0) | |||
{ | |||
outputStream << newLine; | |||
writeSpaces (outputStream, attIndent); | |||
lineLen = 0; | |||
} | |||
const int64 startPos = outputStream.getPosition(); | |||
outputStream.writeByte (' '); | |||
outputStream << att->name; | |||
outputStream.write ("=\"", 2); | |||
escapeIllegalXmlChars (outputStream, att->value, true); | |||
outputStream.writeByte ('"'); | |||
lineLen += (int) (outputStream.getPosition() - startPos); | |||
} | |||
} | |||
if (firstChildElement != nullptr) | |||
{ | |||
outputStream.writeByte ('>'); | |||
bool lastWasTextNode = false; | |||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
{ | |||
if (child->isTextElement()) | |||
{ | |||
escapeIllegalXmlChars (outputStream, child->getText(), false); | |||
lastWasTextNode = true; | |||
} | |||
else | |||
{ | |||
if (indentationLevel >= 0 && ! lastWasTextNode) | |||
outputStream << newLine; | |||
child->writeElementAsText (outputStream, | |||
lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength); | |||
lastWasTextNode = false; | |||
} | |||
} | |||
if (indentationLevel >= 0 && ! lastWasTextNode) | |||
{ | |||
outputStream << newLine; | |||
writeSpaces (outputStream, (size_t) indentationLevel); | |||
} | |||
outputStream.write ("</", 2); | |||
outputStream << tagName; | |||
outputStream.writeByte ('>'); | |||
} | |||
else | |||
{ | |||
outputStream.write ("/>", 2); | |||
} | |||
} | |||
else | |||
{ | |||
escapeIllegalXmlChars (outputStream, getText(), false); | |||
} | |||
} | |||
String XmlElement::createDocument (StringRef dtdToUse, | |||
const bool allOnOneLine, | |||
const bool includeXmlHeader, | |||
StringRef encodingType, | |||
const int lineWrapLength) const | |||
{ | |||
MemoryOutputStream mem (2048); | |||
writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); | |||
return mem.toUTF8(); | |||
} | |||
void XmlElement::writeToStream (OutputStream& output, | |||
StringRef dtdToUse, | |||
const bool allOnOneLine, | |||
const bool includeXmlHeader, | |||
StringRef encodingType, | |||
const int lineWrapLength) const | |||
{ | |||
using namespace XmlOutputFunctions; | |||
if (includeXmlHeader) | |||
{ | |||
output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>"; | |||
if (allOnOneLine) | |||
output.writeByte (' '); | |||
else | |||
output << newLine << newLine; | |||
} | |||
if (dtdToUse.isNotEmpty()) | |||
{ | |||
output << dtdToUse; | |||
if (allOnOneLine) | |||
output.writeByte (' '); | |||
else | |||
output << newLine; | |||
} | |||
writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength); | |||
if (! allOnOneLine) | |||
output << newLine; | |||
} | |||
#if 0 | |||
bool XmlElement::writeToFile (const File& file, | |||
StringRef dtdToUse, | |||
StringRef encodingType, | |||
const int lineWrapLength) const | |||
{ | |||
TemporaryFile tempFile (file); | |||
{ | |||
FileOutputStream out (tempFile.getFile()); | |||
if (! out.openedOk()) | |||
return false; | |||
writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength); | |||
out.flush(); // (called explicitly to force an fsync on posix) | |||
if (out.getStatus().failed()) | |||
return false; | |||
} | |||
return tempFile.overwriteTargetFileWithTemporary(); | |||
} | |||
#endif | |||
//============================================================================== | |||
bool XmlElement::hasTagName (StringRef possibleTagName) const noexcept | |||
{ | |||
const bool matches = tagName.equalsIgnoreCase (possibleTagName); | |||
// XML tags should be case-sensitive, so although this method allows a | |||
// case-insensitive match to pass, you should try to avoid this. | |||
jassert ((! matches) || tagName == possibleTagName); | |||
return matches; | |||
} | |||
String XmlElement::getNamespace() const | |||
{ | |||
return tagName.upToFirstOccurrenceOf (":", false, false); | |||
} | |||
String XmlElement::getTagNameWithoutNamespace() const | |||
{ | |||
return tagName.fromLastOccurrenceOf (":", false, false); | |||
} | |||
bool XmlElement::hasTagNameIgnoringNamespace (StringRef possibleTagName) const | |||
{ | |||
return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName; | |||
} | |||
XmlElement* XmlElement::getNextElementWithTagName (StringRef requiredTagName) const | |||
{ | |||
XmlElement* e = nextListItem; | |||
while (e != nullptr && ! e->hasTagName (requiredTagName)) | |||
e = e->nextListItem; | |||
return e; | |||
} | |||
//============================================================================== | |||
int XmlElement::getNumAttributes() const noexcept | |||
{ | |||
return attributes.size(); | |||
} | |||
static const String& getEmptyStringRef() noexcept | |||
{ | |||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||
return String::empty; | |||
#else | |||
static String empty; | |||
return empty; | |||
#endif | |||
} | |||
const String& XmlElement::getAttributeName (const int index) const noexcept | |||
{ | |||
if (const XmlAttributeNode* const att = attributes [index]) | |||
return att->name.toString(); | |||
return getEmptyStringRef(); | |||
} | |||
const String& XmlElement::getAttributeValue (const int index) const noexcept | |||
{ | |||
if (const XmlAttributeNode* const att = attributes [index]) | |||
return att->value; | |||
return getEmptyStringRef(); | |||
} | |||
XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept | |||
{ | |||
for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||
if (att->name == attributeName) | |||
return att; | |||
return nullptr; | |||
} | |||
bool XmlElement::hasAttribute (StringRef attributeName) const noexcept | |||
{ | |||
return getAttribute (attributeName) != nullptr; | |||
} | |||
//============================================================================== | |||
const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept | |||
{ | |||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
return att->value; | |||
return getEmptyStringRef(); | |||
} | |||
String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const | |||
{ | |||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
return att->value; | |||
return defaultReturnValue; | |||
} | |||
int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const | |||
{ | |||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
return att->value.getIntValue(); | |||
return defaultReturnValue; | |||
} | |||
double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const | |||
{ | |||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
return att->value.getDoubleValue(); | |||
return defaultReturnValue; | |||
} | |||
bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const | |||
{ | |||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
{ | |||
const juce_wchar firstChar = *(att->value.getCharPointer().findEndOfWhitespace()); | |||
return firstChar == '1' | |||
|| firstChar == 't' | |||
|| firstChar == 'y' | |||
|| firstChar == 'T' | |||
|| firstChar == 'Y'; | |||
} | |||
return defaultReturnValue; | |||
} | |||
bool XmlElement::compareAttribute (StringRef attributeName, | |||
StringRef stringToCompareAgainst, | |||
const bool ignoreCase) const noexcept | |||
{ | |||
if (const XmlAttributeNode* att = getAttribute (attributeName)) | |||
return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst) | |||
: att->value == stringToCompareAgainst; | |||
return false; | |||
} | |||
//============================================================================== | |||
void XmlElement::setAttribute (const Identifier& attributeName, const String& value) | |||
{ | |||
if (attributes == nullptr) | |||
{ | |||
attributes = new XmlAttributeNode (attributeName, value); | |||
} | |||
else | |||
{ | |||
for (XmlAttributeNode* att = attributes; ; att = att->nextListItem) | |||
{ | |||
if (att->name == attributeName) | |||
{ | |||
att->value = value; | |||
break; | |||
} | |||
if (att->nextListItem == nullptr) | |||
{ | |||
att->nextListItem = new XmlAttributeNode (attributeName, value); | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
void XmlElement::setAttribute (const Identifier& attributeName, const int number) | |||
{ | |||
setAttribute (attributeName, String (number)); | |||
} | |||
void XmlElement::setAttribute (const Identifier& attributeName, const double number) | |||
{ | |||
setAttribute (attributeName, String (number, 20)); | |||
} | |||
void XmlElement::removeAttribute (const Identifier& attributeName) noexcept | |||
{ | |||
for (LinkedListPointer<XmlAttributeNode>* att = &attributes; | |||
att->get() != nullptr; | |||
att = &(att->get()->nextListItem)) | |||
{ | |||
if (att->get()->name == attributeName) | |||
{ | |||
delete att->removeNext(); | |||
break; | |||
} | |||
} | |||
} | |||
void XmlElement::removeAllAttributes() noexcept | |||
{ | |||
attributes.deleteAll(); | |||
} | |||
//============================================================================== | |||
int XmlElement::getNumChildElements() const noexcept | |||
{ | |||
return firstChildElement.size(); | |||
} | |||
XmlElement* XmlElement::getChildElement (const int index) const noexcept | |||
{ | |||
return firstChildElement [index].get(); | |||
} | |||
XmlElement* XmlElement::getChildByName (StringRef childName) const noexcept | |||
{ | |||
jassert (! childName.isEmpty()); | |||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
if (child->hasTagName (childName)) | |||
return child; | |||
return nullptr; | |||
} | |||
XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept | |||
{ | |||
jassert (! attributeName.isEmpty()); | |||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
if (child->compareAttribute (attributeName, attributeValue)) | |||
return child; | |||
return nullptr; | |||
} | |||
void XmlElement::addChildElement (XmlElement* const newNode) noexcept | |||
{ | |||
if (newNode != nullptr) | |||
{ | |||
// The element being added must not be a child of another node! | |||
jassert (newNode->nextListItem == nullptr); | |||
firstChildElement.append (newNode); | |||
} | |||
} | |||
void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept | |||
{ | |||
if (newNode != nullptr) | |||
{ | |||
// The element being added must not be a child of another node! | |||
jassert (newNode->nextListItem == nullptr); | |||
firstChildElement.insertAtIndex (indexToInsertAt, newNode); | |||
} | |||
} | |||
void XmlElement::prependChildElement (XmlElement* newNode) noexcept | |||
{ | |||
if (newNode != nullptr) | |||
{ | |||
// The element being added must not be a child of another node! | |||
jassert (newNode->nextListItem == nullptr); | |||
firstChildElement.insertNext (newNode); | |||
} | |||
} | |||
XmlElement* XmlElement::createNewChildElement (StringRef childTagName) | |||
{ | |||
XmlElement* const newElement = new XmlElement (childTagName); | |||
addChildElement (newElement); | |||
return newElement; | |||
} | |||
bool XmlElement::replaceChildElement (XmlElement* const currentChildElement, | |||
XmlElement* const newNode) noexcept | |||
{ | |||
if (newNode != nullptr) | |||
{ | |||
if (LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement)) | |||
{ | |||
if (currentChildElement != newNode) | |||
delete p->replaceNext (newNode); | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
void XmlElement::removeChildElement (XmlElement* const childToRemove, | |||
const bool shouldDeleteTheChild) noexcept | |||
{ | |||
if (childToRemove != nullptr) | |||
{ | |||
firstChildElement.remove (childToRemove); | |||
if (shouldDeleteTheChild) | |||
delete childToRemove; | |||
} | |||
} | |||
bool XmlElement::isEquivalentTo (const XmlElement* const other, | |||
const bool ignoreOrderOfAttributes) const noexcept | |||
{ | |||
if (this != other) | |||
{ | |||
if (other == nullptr || tagName != other->tagName) | |||
return false; | |||
if (ignoreOrderOfAttributes) | |||
{ | |||
int totalAtts = 0; | |||
for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) | |||
{ | |||
if (! other->compareAttribute (att->name, att->value)) | |||
return false; | |||
++totalAtts; | |||
} | |||
if (totalAtts != other->getNumAttributes()) | |||
return false; | |||
} | |||
else | |||
{ | |||
const XmlAttributeNode* thisAtt = attributes; | |||
const XmlAttributeNode* otherAtt = other->attributes; | |||
for (;;) | |||
{ | |||
if (thisAtt == nullptr || otherAtt == nullptr) | |||
{ | |||
if (thisAtt == otherAtt) // both nullptr, so it's a match | |||
break; | |||
return false; | |||
} | |||
if (thisAtt->name != otherAtt->name | |||
|| thisAtt->value != otherAtt->value) | |||
{ | |||
return false; | |||
} | |||
thisAtt = thisAtt->nextListItem; | |||
otherAtt = otherAtt->nextListItem; | |||
} | |||
} | |||
const XmlElement* thisChild = firstChildElement; | |||
const XmlElement* otherChild = other->firstChildElement; | |||
for (;;) | |||
{ | |||
if (thisChild == nullptr || otherChild == nullptr) | |||
{ | |||
if (thisChild == otherChild) // both 0, so it's a match | |||
break; | |||
return false; | |||
} | |||
if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes)) | |||
return false; | |||
thisChild = thisChild->nextListItem; | |||
otherChild = otherChild->nextListItem; | |||
} | |||
} | |||
return true; | |||
} | |||
void XmlElement::deleteAllChildElements() noexcept | |||
{ | |||
firstChildElement.deleteAll(); | |||
} | |||
void XmlElement::deleteAllChildElementsWithTagName (StringRef name) noexcept | |||
{ | |||
for (XmlElement* child = firstChildElement; child != nullptr;) | |||
{ | |||
XmlElement* const nextChild = child->nextListItem; | |||
if (child->hasTagName (name)) | |||
removeChildElement (child, true); | |||
child = nextChild; | |||
} | |||
} | |||
bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept | |||
{ | |||
return firstChildElement.contains (possibleChild); | |||
} | |||
XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept | |||
{ | |||
if (this == elementToLookFor || elementToLookFor == nullptr) | |||
return nullptr; | |||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
{ | |||
if (elementToLookFor == child) | |||
return this; | |||
if (XmlElement* const found = child->findParentElementOf (elementToLookFor)) | |||
return found; | |||
} | |||
return nullptr; | |||
} | |||
void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept | |||
{ | |||
firstChildElement.copyToArray (elems); | |||
} | |||
void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept | |||
{ | |||
XmlElement* e = firstChildElement = elems[0]; | |||
for (int i = 1; i < num; ++i) | |||
{ | |||
e->nextListItem = elems[i]; | |||
e = e->nextListItem; | |||
} | |||
e->nextListItem = nullptr; | |||
} | |||
//============================================================================== | |||
bool XmlElement::isTextElement() const noexcept | |||
{ | |||
return tagName.isEmpty(); | |||
} | |||
static const String juce_xmltextContentAttributeName ("text"); | |||
const String& XmlElement::getText() const noexcept | |||
{ | |||
jassert (isTextElement()); // you're trying to get the text from an element that | |||
// isn't actually a text element.. If this contains text sub-nodes, you | |||
// probably want to use getAllSubText instead. | |||
return getStringAttribute (juce_xmltextContentAttributeName); | |||
} | |||
void XmlElement::setText (const String& newText) | |||
{ | |||
if (isTextElement()) | |||
setAttribute (juce_xmltextContentAttributeName, newText); | |||
else | |||
jassertfalse; // you can only change the text in a text element, not a normal one. | |||
} | |||
String XmlElement::getAllSubText() const | |||
{ | |||
if (isTextElement()) | |||
return getText(); | |||
if (getNumChildElements() == 1) | |||
return firstChildElement.get()->getAllSubText(); | |||
MemoryOutputStream mem (1024); | |||
for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) | |||
mem << child->getAllSubText(); | |||
return mem.toUTF8(); | |||
} | |||
String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const | |||
{ | |||
if (const XmlElement* const child = getChildByName (childTagName)) | |||
return child->getAllSubText(); | |||
return defaultReturnValue; | |||
} | |||
XmlElement* XmlElement::createTextElement (const String& text) | |||
{ | |||
XmlElement* const e = new XmlElement ((int) 0); | |||
e->setAttribute (juce_xmltextContentAttributeName, text); | |||
return e; | |||
} | |||
bool XmlElement::isValidXmlName (StringRef text) noexcept | |||
{ | |||
if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance())) | |||
return false; | |||
for (;;) | |||
{ | |||
if (text.isEmpty()) | |||
return true; | |||
if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance())) | |||
return false; | |||
} | |||
} | |||
void XmlElement::addTextElement (const String& text) | |||
{ | |||
addChildElement (createTextElement (text)); | |||
} | |||
void XmlElement::deleteAllTextElements() noexcept | |||
{ | |||
for (XmlElement* child = firstChildElement; child != nullptr;) | |||
{ | |||
XmlElement* const next = child->nextListItem; | |||
if (child->isTextElement()) | |||
removeChildElement (child, true); | |||
child = next; | |||
} | |||
} |
@@ -0,0 +1,775 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||
Permission to use, copy, modify, and/or distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_XMLELEMENT_H_INCLUDED | |||
#define JUCE_XMLELEMENT_H_INCLUDED | |||
//============================================================================== | |||
/** A handy macro to make it easy to iterate all the child elements in an XmlElement. | |||
The parentXmlElement should be a reference to the parent XML, and the childElementVariableName | |||
will be the name of a pointer to each child element. | |||
E.g. @code | |||
XmlElement* myParentXml = createSomeKindOfXmlDocument(); | |||
forEachXmlChildElement (*myParentXml, child) | |||
{ | |||
if (child->hasTagName ("FOO")) | |||
doSomethingWithXmlElement (child); | |||
} | |||
@endcode | |||
@see forEachXmlChildElementWithTagName | |||
*/ | |||
#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \ | |||
\ | |||
for (juce::XmlElement* childElementVariableName = (parentXmlElement).getFirstChildElement(); \ | |||
childElementVariableName != nullptr; \ | |||
childElementVariableName = childElementVariableName->getNextElement()) | |||
/** A macro that makes it easy to iterate all the child elements of an XmlElement | |||
which have a specified tag. | |||
This does the same job as the forEachXmlChildElement macro, but only for those | |||
elements that have a particular tag name. | |||
The parentXmlElement should be a reference to the parent XML, and the childElementVariableName | |||
will be the name of a pointer to each child element. The requiredTagName is the | |||
tag name to match. | |||
E.g. @code | |||
XmlElement* myParentXml = createSomeKindOfXmlDocument(); | |||
forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG") | |||
{ | |||
// the child object is now guaranteed to be a <MYTAG> element.. | |||
doSomethingWithMYTAGElement (child); | |||
} | |||
@endcode | |||
@see forEachXmlChildElement | |||
*/ | |||
#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \ | |||
\ | |||
for (juce::XmlElement* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \ | |||
childElementVariableName != nullptr; \ | |||
childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName)) | |||
//============================================================================== | |||
/** Used to build a tree of elements representing an XML document. | |||
An XML document can be parsed into a tree of XmlElements, each of which | |||
represents an XML tag structure, and which may itself contain other | |||
nested elements. | |||
An XmlElement can also be converted back into a text document, and has | |||
lots of useful methods for manipulating its attributes and sub-elements, | |||
so XmlElements can actually be used as a handy general-purpose data | |||
structure. | |||
Here's an example of parsing some elements: @code | |||
// check we're looking at the right kind of document.. | |||
if (myElement->hasTagName ("ANIMALS")) | |||
{ | |||
// now we'll iterate its sub-elements looking for 'giraffe' elements.. | |||
forEachXmlChildElement (*myElement, e) | |||
{ | |||
if (e->hasTagName ("GIRAFFE")) | |||
{ | |||
// found a giraffe, so use some of its attributes.. | |||
String giraffeName = e->getStringAttribute ("name"); | |||
int giraffeAge = e->getIntAttribute ("age"); | |||
bool isFriendly = e->getBoolAttribute ("friendly"); | |||
} | |||
} | |||
} | |||
@endcode | |||
And here's an example of how to create an XML document from scratch: @code | |||
// create an outer node called "ANIMALS" | |||
XmlElement animalsList ("ANIMALS"); | |||
for (int i = 0; i < numAnimals; ++i) | |||
{ | |||
// create an inner element.. | |||
XmlElement* giraffe = new XmlElement ("GIRAFFE"); | |||
giraffe->setAttribute ("name", "nigel"); | |||
giraffe->setAttribute ("age", 10); | |||
giraffe->setAttribute ("friendly", true); | |||
// ..and add our new element to the parent node | |||
animalsList.addChildElement (giraffe); | |||
} | |||
// now we can turn the whole thing into a text document.. | |||
String myXmlDoc = animalsList.createDocument (String()); | |||
@endcode | |||
@see XmlDocument | |||
*/ | |||
class JUCE_API XmlElement | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an XmlElement with this tag name. */ | |||
explicit XmlElement (const String& tagName); | |||
/** Creates an XmlElement with this tag name. */ | |||
explicit XmlElement (const char* tagName); | |||
/** Creates an XmlElement with this tag name. */ | |||
explicit XmlElement (const Identifier& tagName); | |||
/** Creates an XmlElement with this tag name. */ | |||
explicit XmlElement (StringRef tagName); | |||
/** Creates an XmlElement with this tag name. */ | |||
XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd); | |||
/** Creates a (deep) copy of another element. */ | |||
XmlElement (const XmlElement&); | |||
/** Creates a (deep) copy of another element. */ | |||
XmlElement& operator= (const XmlElement&); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
XmlElement (XmlElement&&) noexcept; | |||
XmlElement& operator= (XmlElement&&) noexcept; | |||
#endif | |||
/** Deleting an XmlElement will also delete all of its child elements. */ | |||
~XmlElement() noexcept; | |||
//============================================================================== | |||
/** Compares two XmlElements to see if they contain the same text and attiributes. | |||
The elements are only considered equivalent if they contain the same attiributes | |||
with the same values, and have the same sub-nodes. | |||
@param other the other element to compare to | |||
@param ignoreOrderOfAttributes if true, this means that two elements with the | |||
same attributes in a different order will be | |||
considered the same; if false, the attributes must | |||
be in the same order as well | |||
*/ | |||
bool isEquivalentTo (const XmlElement* other, | |||
bool ignoreOrderOfAttributes) const noexcept; | |||
//============================================================================== | |||
/** Returns an XML text document that represents this element. | |||
The string returned can be parsed to recreate the same XmlElement that | |||
was used to create it. | |||
@param dtdToUse the DTD to add to the document | |||
@param allOnOneLine if true, this means that the document will not contain any | |||
linefeeds, so it'll be smaller but not very easy to read. | |||
@param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the | |||
document | |||
@param encodingType the character encoding format string to put into the xml | |||
header | |||
@param lineWrapLength the line length that will be used before items get placed on | |||
a new line. This isn't an absolute maximum length, it just | |||
determines how lists of attributes get broken up | |||
@see writeToStream, writeToFile | |||
*/ | |||
String createDocument (StringRef dtdToUse, | |||
bool allOnOneLine = false, | |||
bool includeXmlHeader = true, | |||
StringRef encodingType = "UTF-8", | |||
int lineWrapLength = 60) const; | |||
/** Writes the document to a stream as UTF-8. | |||
@param output the stream to write to | |||
@param dtdToUse the DTD to add to the document | |||
@param allOnOneLine if true, this means that the document will not contain any | |||
linefeeds, so it'll be smaller but not very easy to read. | |||
@param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the | |||
document | |||
@param encodingType the character encoding format string to put into the xml | |||
header | |||
@param lineWrapLength the line length that will be used before items get placed on | |||
a new line. This isn't an absolute maximum length, it just | |||
determines how lists of attributes get broken up | |||
@see writeToFile, createDocument | |||
*/ | |||
void writeToStream (OutputStream& output, | |||
StringRef dtdToUse, | |||
bool allOnOneLine = false, | |||
bool includeXmlHeader = true, | |||
StringRef encodingType = "UTF-8", | |||
int lineWrapLength = 60) const; | |||
/** Writes the element to a file as an XML document. | |||
To improve safety in case something goes wrong while writing the file, this | |||
will actually write the document to a new temporary file in the same | |||
directory as the destination file, and if this succeeds, it will rename this | |||
new file as the destination file (overwriting any existing file that was there). | |||
@param destinationFile the file to write to. If this already exists, it will be | |||
overwritten. | |||
@param dtdToUse the DTD to add to the document | |||
@param encodingType the character encoding format string to put into the xml | |||
header | |||
@param lineWrapLength the line length that will be used before items get placed on | |||
a new line. This isn't an absolute maximum length, it just | |||
determines how lists of attributes get broken up | |||
@returns true if the file is written successfully; false if something goes wrong | |||
in the process | |||
@see createDocument | |||
*/ | |||
bool writeToFile (const File& destinationFile, | |||
StringRef dtdToUse, | |||
StringRef encodingType = "UTF-8", | |||
int lineWrapLength = 60) const; | |||
//============================================================================== | |||
/** Returns this element's tag type name. | |||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would return "MOOSE". | |||
@see hasTagName | |||
*/ | |||
const String& getTagName() const noexcept { return tagName; } | |||
/** Returns the namespace portion of the tag-name, or an empty string if none is specified. */ | |||
String getNamespace() const; | |||
/** Returns the part of the tag-name that follows any namespace declaration. */ | |||
String getTagNameWithoutNamespace() const; | |||
/** Tests whether this element has a particular tag name. | |||
@param possibleTagName the tag name you're comparing it with | |||
@see getTagName | |||
*/ | |||
bool hasTagName (StringRef possibleTagName) const noexcept; | |||
/** Tests whether this element has a particular tag name, ignoring any XML namespace prefix. | |||
So a test for e.g. "xyz" will return true for "xyz" and also "foo:xyz", "bar::xyz", etc. | |||
@see getTagName | |||
*/ | |||
bool hasTagNameIgnoringNamespace (StringRef possibleTagName) const; | |||
//============================================================================== | |||
/** Returns the number of XML attributes this element contains. | |||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would | |||
return 2. | |||
*/ | |||
int getNumAttributes() const noexcept; | |||
/** Returns the name of one of the elements attributes. | |||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, then | |||
getAttributeName(1) would return "antlers". | |||
@see getAttributeValue, getStringAttribute | |||
*/ | |||
const String& getAttributeName (int attributeIndex) const noexcept; | |||
/** Returns the value of one of the elements attributes. | |||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, then | |||
getAttributeName(1) would return "2". | |||
@see getAttributeName, getStringAttribute | |||
*/ | |||
const String& getAttributeValue (int attributeIndex) const noexcept; | |||
//============================================================================== | |||
// Attribute-handling methods.. | |||
/** Checks whether the element contains an attribute with a certain name. */ | |||
bool hasAttribute (StringRef attributeName) const noexcept; | |||
/** Returns the value of a named attribute. | |||
@param attributeName the name of the attribute to look up | |||
*/ | |||
const String& getStringAttribute (StringRef attributeName) const noexcept; | |||
/** Returns the value of a named attribute. | |||
@param attributeName the name of the attribute to look up | |||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||
with this name | |||
*/ | |||
String getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const; | |||
/** Compares the value of a named attribute with a value passed-in. | |||
@param attributeName the name of the attribute to look up | |||
@param stringToCompareAgainst the value to compare it with | |||
@param ignoreCase whether the comparison should be case-insensitive | |||
@returns true if the value of the attribute is the same as the string passed-in; | |||
false if it's different (or if no such attribute exists) | |||
*/ | |||
bool compareAttribute (StringRef attributeName, | |||
StringRef stringToCompareAgainst, | |||
bool ignoreCase = false) const noexcept; | |||
/** Returns the value of a named attribute as an integer. | |||
This will try to find the attribute and convert it to an integer (using | |||
the String::getIntValue() method). | |||
@param attributeName the name of the attribute to look up | |||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||
with this name | |||
@see setAttribute | |||
*/ | |||
int getIntAttribute (StringRef attributeName, int defaultReturnValue = 0) const; | |||
/** Returns the value of a named attribute as floating-point. | |||
This will try to find the attribute and convert it to a double (using | |||
the String::getDoubleValue() method). | |||
@param attributeName the name of the attribute to look up | |||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||
with this name | |||
@see setAttribute | |||
*/ | |||
double getDoubleAttribute (StringRef attributeName, double defaultReturnValue = 0.0) const; | |||
/** Returns the value of a named attribute as a boolean. | |||
This will try to find the attribute and interpret it as a boolean. To do this, | |||
it'll return true if the value is "1", "true", "y", etc, or false for other | |||
values. | |||
@param attributeName the name of the attribute to look up | |||
@param defaultReturnValue a value to return if the element doesn't have an attribute | |||
with this name | |||
*/ | |||
bool getBoolAttribute (StringRef attributeName, bool defaultReturnValue = false) const; | |||
/** Adds a named attribute to the element. | |||
If the element already contains an attribute with this name, it's value will | |||
be updated to the new value. If there's no such attribute yet, a new one will | |||
be added. | |||
Note that there are other setAttribute() methods that take integers, | |||
doubles, etc. to make it easy to store numbers. | |||
@param attributeName the name of the attribute to set | |||
@param newValue the value to set it to | |||
@see removeAttribute | |||
*/ | |||
void setAttribute (const Identifier& attributeName, const String& newValue); | |||
/** Adds a named attribute to the element, setting it to an integer value. | |||
If the element already contains an attribute with this name, it's value will | |||
be updated to the new value. If there's no such attribute yet, a new one will | |||
be added. | |||
Note that there are other setAttribute() methods that take integers, | |||
doubles, etc. to make it easy to store numbers. | |||
@param attributeName the name of the attribute to set | |||
@param newValue the value to set it to | |||
*/ | |||
void setAttribute (const Identifier& attributeName, int newValue); | |||
/** Adds a named attribute to the element, setting it to a floating-point value. | |||
If the element already contains an attribute with this name, it's value will | |||
be updated to the new value. If there's no such attribute yet, a new one will | |||
be added. | |||
Note that there are other setAttribute() methods that take integers, | |||
doubles, etc. to make it easy to store numbers. | |||
@param attributeName the name of the attribute to set | |||
@param newValue the value to set it to | |||
*/ | |||
void setAttribute (const Identifier& attributeName, double newValue); | |||
/** Removes a named attribute from the element. | |||
@param attributeName the name of the attribute to remove | |||
@see removeAllAttributes | |||
*/ | |||
void removeAttribute (const Identifier& attributeName) noexcept; | |||
/** Removes all attributes from this element. */ | |||
void removeAllAttributes() noexcept; | |||
//============================================================================== | |||
// Child element methods.. | |||
/** Returns the first of this element's sub-elements. | |||
see getNextElement() for an example of how to iterate the sub-elements. | |||
@see forEachXmlChildElement | |||
*/ | |||
XmlElement* getFirstChildElement() const noexcept { return firstChildElement; } | |||
/** Returns the next of this element's siblings. | |||
This can be used for iterating an element's sub-elements, e.g. | |||
@code | |||
XmlElement* child = myXmlDocument->getFirstChildElement(); | |||
while (child != nullptr) | |||
{ | |||
...do stuff with this child.. | |||
child = child->getNextElement(); | |||
} | |||
@endcode | |||
Note that when iterating the child elements, some of them might be | |||
text elements as well as XML tags - use isTextElement() to work this | |||
out. | |||
Also, it's much easier and neater to use this method indirectly via the | |||
forEachXmlChildElement macro. | |||
@returns the sibling element that follows this one, or a nullptr if | |||
this is the last element in its parent | |||
@see getNextElement, isTextElement, forEachXmlChildElement | |||
*/ | |||
inline XmlElement* getNextElement() const noexcept { return nextListItem; } | |||
/** Returns the next of this element's siblings which has the specified tag | |||
name. | |||
This is like getNextElement(), but will scan through the list until it | |||
finds an element with the given tag name. | |||
@see getNextElement, forEachXmlChildElementWithTagName | |||
*/ | |||
XmlElement* getNextElementWithTagName (StringRef requiredTagName) const; | |||
/** Returns the number of sub-elements in this element. | |||
@see getChildElement | |||
*/ | |||
int getNumChildElements() const noexcept; | |||
/** Returns the sub-element at a certain index. | |||
It's not very efficient to iterate the sub-elements by index - see | |||
getNextElement() for an example of how best to iterate. | |||
@returns the n'th child of this element, or nullptr if the index is out-of-range | |||
@see getNextElement, isTextElement, getChildByName | |||
*/ | |||
XmlElement* getChildElement (int index) const noexcept; | |||
/** Returns the first sub-element with a given tag-name. | |||
@param tagNameToLookFor the tag name of the element you want to find | |||
@returns the first element with this tag name, or nullptr if none is found | |||
@see getNextElement, isTextElement, getChildElement, getChildByAttribute | |||
*/ | |||
XmlElement* getChildByName (StringRef tagNameToLookFor) const noexcept; | |||
/** Returns the first sub-element which has an attribute that matches the given value. | |||
@param attributeName the name of the attribute to check | |||
@param attributeValue the target value of the attribute | |||
@returns the first element with this attribute value, or nullptr if none is found | |||
@see getChildByName | |||
*/ | |||
XmlElement* getChildByAttribute (StringRef attributeName, | |||
StringRef attributeValue) const noexcept; | |||
//============================================================================== | |||
/** Appends an element to this element's list of children. | |||
Child elements are deleted automatically when their parent is deleted, so | |||
make sure the object that you pass in will not be deleted by anything else, | |||
and make sure it's not already the child of another element. | |||
Note that due to the XmlElement using a singly-linked-list, prependChildElement() | |||
is an O(1) operation, but addChildElement() is an O(N) operation - so if | |||
you're adding large number of elements, you may prefer to do so in reverse order! | |||
@see getFirstChildElement, getNextElement, getNumChildElements, | |||
getChildElement, removeChildElement | |||
*/ | |||
void addChildElement (XmlElement* newChildElement) noexcept; | |||
/** Inserts an element into this element's list of children. | |||
Child elements are deleted automatically when their parent is deleted, so | |||
make sure the object that you pass in will not be deleted by anything else, | |||
and make sure it's not already the child of another element. | |||
@param newChildElement the element to add | |||
@param indexToInsertAt the index at which to insert the new element - if this is | |||
below zero, it will be added to the end of the list | |||
@see addChildElement, insertChildElement | |||
*/ | |||
void insertChildElement (XmlElement* newChildElement, | |||
int indexToInsertAt) noexcept; | |||
/** Inserts an element at the beginning of this element's list of children. | |||
Child elements are deleted automatically when their parent is deleted, so | |||
make sure the object that you pass in will not be deleted by anything else, | |||
and make sure it's not already the child of another element. | |||
Note that due to the XmlElement using a singly-linked-list, prependChildElement() | |||
is an O(1) operation, but addChildElement() is an O(N) operation - so if | |||
you're adding large number of elements, you may prefer to do so in reverse order! | |||
@see addChildElement, insertChildElement | |||
*/ | |||
void prependChildElement (XmlElement* newChildElement) noexcept; | |||
/** Creates a new element with the given name and returns it, after adding it | |||
as a child element. | |||
This is a handy method that means that instead of writing this: | |||
@code | |||
XmlElement* newElement = new XmlElement ("foobar"); | |||
myParentElement->addChildElement (newElement); | |||
@endcode | |||
..you could just write this: | |||
@code | |||
XmlElement* newElement = myParentElement->createNewChildElement ("foobar"); | |||
@endcode | |||
*/ | |||
XmlElement* createNewChildElement (StringRef tagName); | |||
/** Replaces one of this element's children with another node. | |||
If the current element passed-in isn't actually a child of this element, | |||
this will return false and the new one won't be added. Otherwise, the | |||
existing element will be deleted, replaced with the new one, and it | |||
will return true. | |||
*/ | |||
bool replaceChildElement (XmlElement* currentChildElement, | |||
XmlElement* newChildNode) noexcept; | |||
/** Removes a child element. | |||
@param childToRemove the child to look for and remove | |||
@param shouldDeleteTheChild if true, the child will be deleted, if false it'll | |||
just remove it | |||
*/ | |||
void removeChildElement (XmlElement* childToRemove, | |||
bool shouldDeleteTheChild) noexcept; | |||
/** Deletes all the child elements in the element. | |||
@see removeChildElement, deleteAllChildElementsWithTagName | |||
*/ | |||
void deleteAllChildElements() noexcept; | |||
/** Deletes all the child elements with a given tag name. | |||
@see removeChildElement | |||
*/ | |||
void deleteAllChildElementsWithTagName (StringRef tagName) noexcept; | |||
/** Returns true if the given element is a child of this one. */ | |||
bool containsChildElement (const XmlElement* possibleChild) const noexcept; | |||
/** Recursively searches all sub-elements of this one, looking for an element | |||
which is the direct parent of the specified element. | |||
Because elements don't store a pointer to their parent, if you have one | |||
and need to find its parent, the only way to do so is to exhaustively | |||
search the whole tree for it. | |||
If the given child is found somewhere in this element's hierarchy, then | |||
this method will return its parent. If not, it will return nullptr. | |||
*/ | |||
XmlElement* findParentElementOf (const XmlElement* childToSearchFor) noexcept; | |||
//============================================================================== | |||
/** Sorts the child elements using a comparator. | |||
This will use a comparator object to sort the elements into order. The object | |||
passed must have a method of the form: | |||
@code | |||
int compareElements (const XmlElement* first, const XmlElement* second); | |||
@endcode | |||
..and this method must return: | |||
- a value of < 0 if the first comes before the second | |||
- a value of 0 if the two objects are equivalent | |||
- a value of > 0 if the second comes before the first | |||
To improve performance, the compareElements() method can be declared as static or const. | |||
@param comparator the comparator to use for comparing elements. | |||
@param retainOrderOfEquivalentItems if this is true, then items which the comparator | |||
says are equivalent will be kept in the order in which they | |||
currently appear in the array. This is slower to perform, but | |||
may be important in some cases. If it's false, a faster algorithm | |||
is used, but equivalent elements may be rearranged. | |||
*/ | |||
template <class ElementComparator> | |||
void sortChildElements (ElementComparator& comparator, | |||
bool retainOrderOfEquivalentItems = false) | |||
{ | |||
const int num = getNumChildElements(); | |||
if (num > 1) | |||
{ | |||
HeapBlock<XmlElement*> elems ((size_t) num); | |||
getChildElementsAsArray (elems); | |||
sortArray (comparator, (XmlElement**) elems, 0, num - 1, retainOrderOfEquivalentItems); | |||
reorderChildElements (elems, num); | |||
} | |||
} | |||
//============================================================================== | |||
/** Returns true if this element is a section of text. | |||
Elements can either be an XML tag element or a secton of text, so this | |||
is used to find out what kind of element this one is. | |||
@see getAllText, addTextElement, deleteAllTextElements | |||
*/ | |||
bool isTextElement() const noexcept; | |||
/** Returns the text for a text element. | |||
Note that if you have an element like this: | |||
@code<xyz>hello</xyz>@endcode | |||
then calling getText on the "xyz" element won't return "hello", because that is | |||
actually stored in a special text sub-element inside the xyz element. To get the | |||
"hello" string, you could either call getText on the (unnamed) sub-element, or | |||
use getAllSubText() to do this automatically. | |||
Note that leading and trailing whitespace will be included in the string - to remove | |||
if, just call String::trim() on the result. | |||
@see isTextElement, getAllSubText, getChildElementAllSubText | |||
*/ | |||
const String& getText() const noexcept; | |||
/** Sets the text in a text element. | |||
Note that this is only a valid call if this element is a text element. If it's | |||
not, then no action will be performed. If you're trying to add text inside a normal | |||
element, you probably want to use addTextElement() instead. | |||
*/ | |||
void setText (const String& newText); | |||
/** Returns all the text from this element's child nodes. | |||
This iterates all the child elements and when it finds text elements, | |||
it concatenates their text into a big string which it returns. | |||
E.g. @code<xyz>hello <x>there</x> world</xyz>@endcode | |||
if you called getAllSubText on the "xyz" element, it'd return "hello there world". | |||
Note that leading and trailing whitespace will be included in the string - to remove | |||
if, just call String::trim() on the result. | |||
@see isTextElement, getChildElementAllSubText, getText, addTextElement | |||
*/ | |||
String getAllSubText() const; | |||
/** Returns all the sub-text of a named child element. | |||
If there is a child element with the given tag name, this will return | |||
all of its sub-text (by calling getAllSubText() on it). If there is | |||
no such child element, this will return the default string passed-in. | |||
@see getAllSubText | |||
*/ | |||
String getChildElementAllSubText (StringRef childTagName, | |||
const String& defaultReturnValue) const; | |||
/** Appends a section of text to this element. | |||
@see isTextElement, getText, getAllSubText | |||
*/ | |||
void addTextElement (const String& text); | |||
/** Removes all the text elements from this element. | |||
@see isTextElement, getText, getAllSubText, addTextElement | |||
*/ | |||
void deleteAllTextElements() noexcept; | |||
/** Creates a text element that can be added to a parent element. */ | |||
static XmlElement* createTextElement (const String& text); | |||
/** Checks if a given string is a valid XML name */ | |||
static bool isValidXmlName (StringRef possibleName) noexcept; | |||
//============================================================================== | |||
private: | |||
struct XmlAttributeNode | |||
{ | |||
XmlAttributeNode (const XmlAttributeNode&) noexcept; | |||
XmlAttributeNode (const Identifier&, const String&) noexcept; | |||
XmlAttributeNode (String::CharPointerType, String::CharPointerType); | |||
LinkedListPointer<XmlAttributeNode> nextListItem; | |||
Identifier name; | |||
String value; | |||
private: | |||
XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION; | |||
}; | |||
friend class XmlDocument; | |||
friend class LinkedListPointer<XmlAttributeNode>; | |||
friend class LinkedListPointer<XmlElement>; | |||
friend class LinkedListPointer<XmlElement>::Appender; | |||
friend class NamedValueSet; | |||
LinkedListPointer<XmlElement> nextListItem; | |||
LinkedListPointer<XmlElement> firstChildElement; | |||
LinkedListPointer<XmlAttributeNode> attributes; | |||
String tagName; | |||
XmlElement (int) noexcept; | |||
void copyChildrenAndAttributesFrom (const XmlElement&); | |||
void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const; | |||
void getChildElementsAsArray (XmlElement**) const noexcept; | |||
void reorderChildElements (XmlElement**, int) noexcept; | |||
XmlAttributeNode* getAttribute (StringRef) const noexcept; | |||
// Sigh.. L"" or _T("") string literals are problematic in general, and really inappropriate | |||
// for XML tags. Use a UTF-8 encoded literal instead, or if you're really determined to use | |||
// UTF-16, cast it to a String and use the other constructor. | |||
XmlElement (const wchar_t*) JUCE_DELETED_FUNCTION; | |||
JUCE_LEAK_DETECTOR (XmlElement) | |||
}; | |||
#endif // JUCE_XMLELEMENT_H_INCLUDED |