Browse Source

Refactored the StringPool and Identifier classes to store the identifiers as Strings, so that they can be shared with other classes like XmlElement without creating temporary or copied String objects. Also added garbage collection for the pooled strings, and changed XmlElement to pool all of the strings it uses, to reduce memory footprint in large XML trees with many identical names. Also refactored NamedValueSet to use an array instead of a linked list.

tags/2021-05-28
jules 11 years ago
parent
commit
4317f60173
15 changed files with 380 additions and 375 deletions
  1. +2
    -2
      extras/Demo/Source/Demos/XMLandJSONDemo.cpp
  2. +21
    -33
      modules/juce_core/containers/juce_DynamicObject.cpp
  3. +88
    -141
      modules/juce_core/containers/juce_NamedValueSet.cpp
  4. +22
    -42
      modules/juce_core/containers/juce_NamedValueSet.h
  5. +2
    -2
      modules/juce_core/javascript/juce_JSON.cpp
  6. +14
    -18
      modules/juce_core/text/juce_Identifier.cpp
  7. +20
    -18
      modules/juce_core/text/juce_Identifier.h
  8. +11
    -1
      modules/juce_core/text/juce_String.cpp
  9. +6
    -1
      modules/juce_core/text/juce_String.h
  10. +96
    -53
      modules/juce_core/text/juce_StringPool.cpp
  11. +18
    -21
      modules/juce_core/text/juce_StringPool.h
  12. +2
    -3
      modules/juce_core/xml/juce_XmlDocument.cpp
  13. +39
    -14
      modules/juce_core/xml/juce_XmlElement.cpp
  14. +38
    -25
      modules/juce_core/xml/juce_XmlElement.h
  15. +1
    -1
      modules/juce_data_structures/values/juce_ValueTree.cpp

+ 2
- 2
extras/Demo/Source/Demos/XMLandJSONDemo.cpp View File

@@ -149,7 +149,7 @@ public:
{ {
var& child (json[i]); var& child (json[i]);
jassert (! child.isVoid()); jassert (! child.isVoid());
addSubItem (new JsonTreeItem (Identifier::null, child));
addSubItem (new JsonTreeItem (Identifier(), child));
} }
} }
else if (DynamicObject* obj = json.getDynamicObject()) else if (DynamicObject* obj = json.getDynamicObject())
@@ -349,7 +349,7 @@ private:
return nullptr; return nullptr;
} }
return new JsonTreeItem (Identifier::null, parsedJson);
return new JsonTreeItem (Identifier(), parsedJson);
} }
/** Clears the editor and loads some default text. */ /** Clears the editor and loads some default text. */


+ 21
- 33
modules/juce_core/containers/juce_DynamicObject.cpp View File

@@ -85,16 +85,9 @@ void DynamicObject::clear()
void DynamicObject::cloneAllProperties() void DynamicObject::cloneAllProperties()
{ {
for (LinkedListPointer<NamedValueSet::NamedValue>* i = &(properties.values);;)
{
if (NamedValueSet::NamedValue* const v = i->get())
{
v->value = v->value.clone();
i = &(v->nextListItem);
}
else
break;
}
for (int i = properties.size(); --i >= 0;)
if (var* v = properties.getVarPointerAt (i))
*v = v->clone();
} }
DynamicObject::Ptr DynamicObject::clone() DynamicObject::Ptr DynamicObject::clone()
@@ -110,32 +103,27 @@ void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const
if (! allOnOneLine) if (! allOnOneLine)
out << newLine; out << newLine;
for (LinkedListPointer<NamedValueSet::NamedValue>* i = &(properties.values);;)
const int numValues = properties.size();
for (int i = 0; i < numValues; ++i)
{ {
if (NamedValueSet::NamedValue* const v = i->get())
if (! allOnOneLine)
JSONFormatter::writeSpaces (out, indentLevel + JSONFormatter::indentSize);
out << '"';
JSONFormatter::writeString (out, properties.getName (i));
out << "\": ";
JSONFormatter::write (out, properties.getValueAt (i), indentLevel + JSONFormatter::indentSize, allOnOneLine);
if (i < numValues - 1)
{ {
if (! allOnOneLine)
JSONFormatter::writeSpaces (out, indentLevel + JSONFormatter::indentSize);
out << '"';
JSONFormatter::writeString (out, v->name);
out << "\": ";
JSONFormatter::write (out, v->value, indentLevel + JSONFormatter::indentSize, allOnOneLine);
if (v->nextListItem.get() != nullptr)
{
if (allOnOneLine)
out << ", ";
else
out << ',' << newLine;
}
else if (! allOnOneLine)
out << newLine;
i = &(v->nextListItem);
if (allOnOneLine)
out << ", ";
else
out << ',' << newLine;
} }
else
break;
else if (! allOnOneLine)
out << newLine;
} }
if (! allOnOneLine) if (! allOnOneLine)


+ 88
- 141
modules/juce_core/containers/juce_NamedValueSet.cpp View File

@@ -26,53 +26,37 @@
============================================================================== ==============================================================================
*/ */
NamedValueSet::NamedValue::NamedValue() noexcept
struct NamedValueSet::NamedValue
{ {
}
inline NamedValueSet::NamedValue::NamedValue (Identifier n, const var& v)
: name (n), value (v)
{
}
NamedValueSet::NamedValue::NamedValue (const NamedValue& other)
: name (other.name), value (other.value)
{
}
NamedValueSet::NamedValue& NamedValueSet::NamedValue::operator= (const NamedValueSet::NamedValue& other)
{
name = other.name;
value = other.value;
return *this;
}
NamedValue() noexcept {}
NamedValue (Identifier n, const var& v) : name (n), value (v) {}
NamedValue (const NamedValue& other) : name (other.name), value (other.value) {}
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
NamedValue (NamedValue&& other) noexcept
: name (static_cast<Identifier&&> (other.name)),
value (static_cast<var&&> (other.value))
{
}
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
NamedValueSet::NamedValue::NamedValue (NamedValue&& other) noexcept
: nextListItem (static_cast<LinkedListPointer<NamedValue>&&> (other.nextListItem)),
name (static_cast<Identifier&&> (other.name)),
value (static_cast<var&&> (other.value))
{
}
NamedValue (Identifier n, var&& v) : name (n), value (static_cast<var&&> (v))
{
}
inline NamedValueSet::NamedValue::NamedValue (Identifier n, var&& v)
: name (n), value (static_cast<var&&> (v))
{
}
NamedValue& operator= (NamedValue&& other) noexcept
{
name = static_cast<Identifier&&> (other.name);
value = static_cast<var&&> (other.value);
return *this;
}
#endif
NamedValueSet::NamedValue& NamedValueSet::NamedValue::operator= (NamedValue&& other) noexcept
{
nextListItem = static_cast<LinkedListPointer<NamedValue>&&> (other.nextListItem);
name = static_cast<Identifier&&> (other.name);
value = static_cast<var&&> (other.value);
return *this;
}
#endif
bool operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; }
bool operator!= (const NamedValue& other) const noexcept { return ! operator== (other); }
bool NamedValueSet::NamedValue::operator== (const NamedValueSet::NamedValue& other) const noexcept
{
return name == other.name && value == other.value;
}
Identifier name;
var value;
};
//============================================================================== //==============================================================================
NamedValueSet::NamedValueSet() noexcept NamedValueSet::NamedValueSet() noexcept
@@ -80,20 +64,20 @@ NamedValueSet::NamedValueSet() noexcept
} }
NamedValueSet::NamedValueSet (const NamedValueSet& other) NamedValueSet::NamedValueSet (const NamedValueSet& other)
: values (other.values)
{ {
values.addCopyOfList (other.values);
} }
NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other)
{ {
clear(); clear();
values.addCopyOfList (other.values);
values = other.values;
return *this; return *this;
} }
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept
: values (static_cast <LinkedListPointer<NamedValue>&&> (other.values))
: values (static_cast <Array<NamedValue>&&> (other.values))
{ {
} }
@@ -111,24 +95,12 @@ NamedValueSet::~NamedValueSet()
void NamedValueSet::clear() void NamedValueSet::clear()
{ {
values.deleteAll();
values.clear();
} }
bool NamedValueSet::operator== (const NamedValueSet& other) const bool NamedValueSet::operator== (const NamedValueSet& other) const
{ {
const NamedValue* i1 = values;
const NamedValue* i2 = other.values;
while (i1 != nullptr && i2 != nullptr)
{
if (! (*i1 == *i2))
return false;
i1 = i1->nextListItem;
i2 = i2->nextListItem;
}
return true;
return values == other.values;
} }
bool NamedValueSet::operator!= (const NamedValueSet& other) const bool NamedValueSet::operator!= (const NamedValueSet& other) const
@@ -141,16 +113,15 @@ int NamedValueSet::size() const noexcept
return values.size(); return values.size();
} }
const var& NamedValueSet::operator[] (Identifier name) const
const var& NamedValueSet::operator[] (const Identifier& name) const
{ {
for (NamedValue* i = values; i != nullptr; i = i->nextListItem)
if (i->name == name)
return i->value;
if (const var* v = getVarPointer (name))
return *v;
return var::null; return var::null;
} }
var NamedValueSet::getWithDefault (Identifier name, const var& defaultReturnValue) const
var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const
{ {
if (const var* const v = getVarPointer (name)) if (const var* const v = getVarPointer (name))
return *v; return *v;
@@ -158,9 +129,9 @@ var NamedValueSet::getWithDefault (Identifier name, const var& defaultReturnValu
return defaultReturnValue; return defaultReturnValue;
} }
var* NamedValueSet::getVarPointer (Identifier name) const noexcept
var* NamedValueSet::getVarPointer (const Identifier& name) const noexcept
{ {
for (NamedValue* i = values; i != nullptr; i = i->nextListItem)
for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i)
if (i->name == name) if (i->name == name)
return &(i->value); return &(i->value);
@@ -170,145 +141,121 @@ var* NamedValueSet::getVarPointer (Identifier name) const noexcept
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
bool NamedValueSet::set (Identifier name, var&& newValue) bool NamedValueSet::set (Identifier name, var&& newValue)
{ {
LinkedListPointer<NamedValue>* i = &values;
while (i->get() != nullptr)
if (var* const v = getVarPointer (name))
{ {
NamedValue* const v = i->get();
if (v->name == name)
{
if (v->value.equalsWithSameType (newValue))
return false;
v->value = static_cast <var&&> (newValue);
return true;
}
if (v->equalsWithSameType (newValue))
return false;
i = &(v->nextListItem);
*v = static_cast<var&&> (newValue);
return true;
} }
i->insertNext (new NamedValue (name, static_cast <var&&> (newValue)));
values.add (NamedValue (name, static_cast<var&&> (newValue)));
return true; return true;
} }
#endif #endif
bool NamedValueSet::set (Identifier name, const var& newValue) bool NamedValueSet::set (Identifier name, const var& newValue)
{ {
LinkedListPointer<NamedValue>* i = &values;
while (i->get() != nullptr)
if (var* const v = getVarPointer (name))
{ {
NamedValue* const v = i->get();
if (v->name == name)
{
if (v->value.equalsWithSameType (newValue))
return false;
v->value = newValue;
return true;
}
if (v->equalsWithSameType (newValue))
return false;
i = &(v->nextListItem);
*v = newValue;
return true;
} }
i->insertNext (new NamedValue (name, newValue));
values.add (NamedValue (name, newValue));
return true; return true;
} }
bool NamedValueSet::contains (Identifier name) const
bool NamedValueSet::contains (const Identifier& name) const
{ {
return getVarPointer (name) != nullptr; return getVarPointer (name) != nullptr;
} }
int NamedValueSet::indexOf (Identifier name) const noexcept
int NamedValueSet::indexOf (const Identifier& name) const noexcept
{ {
int index = 0;
for (NamedValue* i = values; i != nullptr; i = i->nextListItem)
{
if (i->name == name)
return index;
const int numValues = values.size();
++index;
}
for (int i = 0; i < numValues; ++i)
if (values.getReference(i).name == name)
return i;
return -1; return -1;
} }
bool NamedValueSet::remove (Identifier name)
bool NamedValueSet::remove (const Identifier& name)
{ {
LinkedListPointer<NamedValue>* i = &values;
const int numValues = values.size();
for (;;)
for (int i = 0; i < numValues; ++i)
{ {
NamedValue* const v = i->get();
if (v == nullptr)
break;
if (v->name == name)
if (values.getReference(i).name == name)
{ {
delete i->removeNext();
values.remove (i);
return true; return true;
} }
i = &(v->nextListItem);
} }
return false; return false;
} }
Identifier NamedValueSet::getName (const int index) const
Identifier NamedValueSet::getName (const int index) const noexcept
{ {
const NamedValue* const v = values[index];
jassert (v != nullptr);
return v->name;
if (isPositiveAndBelow (index, values.size()))
return values.getReference (index).name;
jassertfalse;
return Identifier();
} }
const var& NamedValueSet::getValueAt (const int index) const
const var& NamedValueSet::getValueAt (const int index) const noexcept
{ {
const NamedValue* const v = values[index];
jassert (v != nullptr);
return v->value;
if (isPositiveAndBelow (index, values.size()))
return values.getReference (index).value;
jassertfalse;
return var::null;
} }
void NamedValueSet::setFromXmlAttributes (const XmlElement& xml)
var* NamedValueSet::getVarPointerAt (int index) const noexcept
{ {
clear();
LinkedListPointer<NamedValue>::Appender appender (values);
if (isPositiveAndBelow (index, values.size()))
return &(values.getReference (index).value);
return nullptr;
}
const int numAtts = xml.getNumAttributes(); // xxx inefficient - should write an att iterator..
void NamedValueSet::setFromXmlAttributes (const XmlElement& xml)
{
values.clearQuick();
for (int i = 0; i < numAtts; ++i)
for (const XmlElement::XmlAttributeNode* att = xml.attributes; att != nullptr; att = att->nextListItem)
{ {
const String& name = xml.getAttributeName (i);
const String& value = xml.getAttributeValue (i);
if (name.startsWith ("base64:"))
if (att->name.toString().startsWith ("base64:"))
{ {
MemoryBlock mb; MemoryBlock mb;
if (mb.fromBase64Encoding (value))
if (mb.fromBase64Encoding (att->value))
{ {
appender.append (new NamedValue (name.substring (7), var (mb)));
values.add (NamedValue (att->name.toString().substring (7), var (mb)));
continue; continue;
} }
} }
appender.append (new NamedValue (name, var (value)));
values.add (NamedValue (att->name, var (att->value)));
} }
} }
void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const
{ {
for (NamedValue* i = values; i != nullptr; i = i->nextListItem)
for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i)
{ {
if (const MemoryBlock* mb = i->value.getBinaryData()) if (const MemoryBlock* mb = i->value.getBinaryData())
{ {
xml.setAttribute ("base64:" + i->name.toString(),
mb->toBase64Encoding());
xml.setAttribute ("base64:" + i->name.toString(), mb->toBase64Encoding());
} }
else else
{ {


+ 22
- 42
modules/juce_core/containers/juce_NamedValueSet.h View File

@@ -67,12 +67,12 @@ public:
If the name isn't found, this will return a void variant. If the name isn't found, this will return a void variant.
@see getProperty @see getProperty
*/ */
const var& operator[] (Identifier name) const;
const var& operator[] (const Identifier& name) const;
/** Tries to return the named value, but if no such value is found, this will /** Tries to return the named value, but if no such value is found, this will
instead return the supplied default value. instead return the supplied default value.
*/ */
var getWithDefault (Identifier name, const var& defaultReturnValue) const;
var getWithDefault (const Identifier& name, const var& defaultReturnValue) const;
/** Changes or adds a named value. /** Changes or adds a named value.
@returns true if a value was changed or added; false if the @returns true if a value was changed or added; false if the
@@ -89,39 +89,43 @@ public:
#endif #endif
/** Returns true if the set contains an item with the specified name. */ /** Returns true if the set contains an item with the specified name. */
bool contains (Identifier name) const;
bool contains (const Identifier& name) const;
/** Removes a value from the set. /** Removes a value from the set.
@returns true if a value was removed; false if there was no value @returns true if a value was removed; false if there was no value
with the name that was given. with the name that was given.
*/ */
bool remove (Identifier name);
bool remove (const Identifier& name);
/** Returns the name of the value at a given index. /** Returns the name of the value at a given index.
The index must be between 0 and size() - 1. The index must be between 0 and size() - 1.
*/ */
Identifier getName (int index) const;
Identifier getName (int index) const noexcept;
/** Returns a pointer to the var that holds a named value, or null if there is
no value with this name.
Do not use this method unless you really need access to the internal var object
for some reason - for normal reading and writing always prefer operator[]() and set().
*/
var* getVarPointer (const Identifier& name) const noexcept;
/** Returns the value of the item at a given index. /** Returns the value of the item at a given index.
The index must be between 0 and size() - 1. The index must be between 0 and size() - 1.
*/ */
const var& getValueAt (int index) const;
const var& getValueAt (int index) const noexcept;
/** Returns the value of the item at a given index.
The index must be between 0 and size() - 1, or this will return a nullptr
*/
var* getVarPointerAt (int index) const noexcept;
/** Returns the index of the given name, or -1 if it's not found. */ /** Returns the index of the given name, or -1 if it's not found. */
int indexOf (Identifier name) const noexcept;
int indexOf (const Identifier& name) const noexcept;
/** Removes all values. */ /** Removes all values. */
void clear(); void clear();
//==============================================================================
/** Returns a pointer to the var that holds a named value, or null if there is
no value with this name.
Do not use this method unless you really need access to the internal var object
for some reason - for normal reading and writing always prefer operator[]() and set().
*/
var* getVarPointer (Identifier name) const noexcept;
//============================================================================== //==============================================================================
/** Sets properties to the values of all of an XML element's attributes. */ /** Sets properties to the values of all of an XML element's attributes. */
void setFromXmlAttributes (const XmlElement& xml); void setFromXmlAttributes (const XmlElement& xml);
@@ -133,32 +137,8 @@ public:
private: private:
//============================================================================== //==============================================================================
class NamedValue
{
public:
NamedValue() noexcept;
NamedValue (const NamedValue&);
NamedValue (Identifier, const var&);
NamedValue& operator= (const NamedValue&);
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
NamedValue (NamedValue&&) noexcept;
NamedValue (Identifier, var&&);
NamedValue& operator= (NamedValue&&) noexcept;
#endif
bool operator== (const NamedValue&) const noexcept;
LinkedListPointer<NamedValue> nextListItem;
Identifier name;
var value;
private:
JUCE_LEAK_DETECTOR (NamedValue)
};
friend class LinkedListPointer<NamedValue>;
LinkedListPointer<NamedValue> values;
friend class DynamicObject;
struct NamedValue;
Array<NamedValue> values;
}; };


+ 2
- 2
modules/juce_core/javascript/juce_JSON.cpp View File

@@ -243,9 +243,9 @@ private:
if (r.failed()) if (r.failed())
return r; return r;
const String propertyName (propertyNameVar.toString());
const Identifier propertyName (propertyNameVar.toString());
if (propertyName.isNotEmpty())
if (propertyName.isValid())
{ {
t = t.findEndOfWhitespace(); t = t.findEndOfWhitespace();
oldT = t; oldT = t;


+ 14
- 18
modules/juce_core/text/juce_Identifier.cpp View File

@@ -26,21 +26,10 @@
============================================================================== ==============================================================================
*/ */
StringPool& Identifier::getPool()
{
static StringPool pool;
return pool;
}
Identifier::Identifier() noexcept {}
Identifier::~Identifier() noexcept {}
Identifier::Identifier() noexcept
: name (nullptr)
{
}
Identifier::Identifier (const Identifier& other) noexcept
: name (other.name)
{
}
Identifier::Identifier (const Identifier& other) noexcept : name (other.name) {}
Identifier& Identifier::operator= (const Identifier other) noexcept Identifier& Identifier::operator= (const Identifier other) noexcept
{ {
@@ -49,20 +38,27 @@ Identifier& Identifier::operator= (const Identifier other) noexcept
} }
Identifier::Identifier (const String& nm) Identifier::Identifier (const String& nm)
: name (Identifier::getPool().getPooledString (nm))
: name (StringPool::getGlobalPool().getPooledString (nm))
{ {
/* An Identifier string must be suitable for use as a script variable or XML
attribute, so it can only contain this limited set of characters.. */
jassert (isValidIdentifier (toString()));
} }
Identifier::Identifier (const char* const nm)
: name (Identifier::getPool().getPooledString (nm))
Identifier::Identifier (const char* nm)
: name (StringPool::getGlobalPool().getPooledString (nm))
{ {
/* An Identifier string must be suitable for use as a script variable or XML /* An Identifier string must be suitable for use as a script variable or XML
attribute, so it can only contain this limited set of characters.. */ attribute, so it can only contain this limited set of characters.. */
jassert (isValidIdentifier (toString())); jassert (isValidIdentifier (toString()));
} }
Identifier::~Identifier()
Identifier::Identifier (String::CharPointerType start, String::CharPointerType end)
: name (StringPool::getGlobalPool().getPooledString (start, end))
{ {
/* An Identifier string must be suitable for use as a script variable or XML
attribute, so it can only contain this limited set of characters.. */
jassert (isValidIdentifier (toString()));
} }
Identifier Identifier::null; Identifier Identifier::null;


+ 20
- 18
modules/juce_core/text/juce_Identifier.h View File

@@ -34,9 +34,9 @@
/** /**
Represents a string identifier, designed for accessing properties by name. Represents a string identifier, designed for accessing properties by name.
Identifier objects are very light and fast to copy, but slower to initialise
from a string, so it's much faster to keep a static identifier object to refer
to frequently-used names, rather than constructing them each time you need it.
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 @see NamedValueSet, ValueTree
*/ */
@@ -58,6 +58,12 @@ public:
*/ */
Identifier (const String& name); 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. */ /** Creates a copy of another identifier. */
Identifier (const Identifier& other) noexcept; Identifier (const Identifier& other) noexcept;
@@ -65,37 +71,37 @@ public:
Identifier& operator= (const Identifier other) noexcept; Identifier& operator= (const Identifier other) noexcept;
/** Destructor */ /** Destructor */
~Identifier();
~Identifier() noexcept;
/** Compares two identifiers. This is a very fast operation. */ /** Compares two identifiers. This is a very fast operation. */
inline bool operator== (Identifier other) const noexcept { return name == other.name; }
inline bool operator== (Identifier other) const noexcept { return name.getCharPointer() == other.name.getCharPointer(); }
/** Compares two identifiers. This is a very fast operation. */ /** Compares two identifiers. This is a very fast operation. */
inline bool operator!= (Identifier other) const noexcept { return name != other.name; }
inline bool operator!= (Identifier other) const noexcept { return name.getCharPointer() != other.name.getCharPointer(); }
/** Compares the identifier with a string. */ /** Compares the identifier with a string. */
inline bool operator== (StringRef other) const noexcept { return name.compare (other.text) == 0; }
inline bool operator== (StringRef other) const noexcept { return name == other; }
/** Compares the identifier with a string. */ /** Compares the identifier with a string. */
inline bool operator!= (StringRef other) const noexcept { return name.compare (other.text) != 0; }
inline bool operator!= (StringRef other) const noexcept { return name != other; }
/** Returns this identifier as a string. */ /** Returns this identifier as a string. */
String toString() const { return name; }
const String& toString() const noexcept { return name; }
/** Returns this identifier's raw string pointer. */ /** Returns this identifier's raw string pointer. */
operator String::CharPointerType() const noexcept { return name; }
operator String::CharPointerType() const noexcept { return name.getCharPointer(); }
/** Returns this identifier's raw string pointer. */ /** Returns this identifier's raw string pointer. */
String::CharPointerType getCharPointer() const noexcept { return name; }
String::CharPointerType getCharPointer() const noexcept { return name.getCharPointer(); }
/** Returns this identifier as a StringRef. */ /** Returns this identifier as a StringRef. */
operator StringRef() const noexcept { return name; } operator StringRef() const noexcept { return name; }
/** Returns true if this Identifier is not null */ /** Returns true if this Identifier is not null */
bool isValid() const noexcept { return name.getAddress() != nullptr; }
bool isValid() const noexcept { return name.isNotEmpty(); }
/** Returns true if this Identifier is null */ /** Returns true if this Identifier is null */
bool isNull() const noexcept { return name.getAddress() == nullptr; }
bool isNull() const noexcept { return name.isEmpty(); }
/** A null identifier. */ /** A null identifier. */
static Identifier null; static Identifier null;
@@ -106,12 +112,8 @@ public:
*/ */
static bool isValidIdentifier (const String& possibleIdentifier) noexcept; static bool isValidIdentifier (const String& possibleIdentifier) noexcept;
private: private:
//==============================================================================
String::CharPointerType name;
static StringPool& getPool();
String name;
}; };


+ 11
- 1
modules/juce_core/text/juce_String.cpp View File

@@ -180,6 +180,11 @@ public:
release (bufferFromText (text)); release (bufferFromText (text));
} }
static inline int getReferenceCount (const CharPointerType text) noexcept
{
return bufferFromText (text)->refCount.get() + 1;
}
//============================================================================== //==============================================================================
static CharPointerType makeUniqueWithByteSize (const CharPointerType text, size_t numBytes) static CharPointerType makeUniqueWithByteSize (const CharPointerType text, size_t numBytes)
{ {
@@ -285,7 +290,7 @@ String& String::operator= (String&& other) noexcept
} }
#endif #endif
inline String::PreallocationBytes::PreallocationBytes (const size_t numBytes_) : numBytes (numBytes_) {}
inline String::PreallocationBytes::PreallocationBytes (const size_t num) noexcept : numBytes (num) {}
String::String (const PreallocationBytes& preallocationSize) String::String (const PreallocationBytes& preallocationSize)
: text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType))) : text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType)))
@@ -297,6 +302,11 @@ void String::preallocateBytes (const size_t numBytesNeeded)
text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType)); text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType));
} }
int String::getReferenceCount() const noexcept
{
return StringHolder::getReferenceCount (text);
}
//============================================================================== //==============================================================================
String::String (const char* const t) String::String (const char* const t)
: text (StringHolder::createFromCharPointer (CharPointer_ASCII (t))) : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t)))


+ 6
- 1
modules/juce_core/text/juce_String.h View File

@@ -1218,6 +1218,11 @@ public:
String convertToPrecomposedUnicode() const; String convertToPrecomposedUnicode() const;
#endif #endif
/** Returns the number of String objects which are currently sharing the same internal
data as this one.
*/
int getReferenceCount() const noexcept;
private: private:
//============================================================================== //==============================================================================
CharPointerType text; CharPointerType text;
@@ -1225,7 +1230,7 @@ private:
//============================================================================== //==============================================================================
struct PreallocationBytes struct PreallocationBytes
{ {
explicit PreallocationBytes (size_t);
explicit PreallocationBytes (size_t) noexcept;
size_t numBytes; size_t numBytes;
}; };


+ 96
- 53
modules/juce_core/text/juce_StringPool.cpp View File

@@ -26,88 +26,131 @@
============================================================================== ==============================================================================
*/ */
StringPool::StringPool() noexcept {}
StringPool::~StringPool() {}
static const uint32 minNumberOfStringsForGarbageCollection = 300;
static const uint32 garbageCollectionInterval = 30000;
namespace StringPoolHelpers
StringPool::StringPool() noexcept : lastGarbageCollectionTime (0) {}
StringPool::~StringPool() {}
struct StartEndString
{ {
template <class StringType>
String::CharPointerType getPooledStringFromArray (Array<String>& strings,
StringType newString,
const CriticalSection& lock)
{
const ScopedLock sl (lock);
int start = 0;
int end = strings.size();
StartEndString (String::CharPointerType s, String::CharPointerType e) noexcept : start (s), end (e) {}
operator String() const { return String (start, end); }
for (;;)
{
if (start >= end)
{
jassert (start <= end);
strings.insert (start, newString);
return strings.getReference (start).getCharPointer();
}
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.toUTF8()); }
static int compareStrings (const StartEndString& string1, const String& string2) noexcept
{
String::CharPointerType s1 (string1.start), s2 (string2.getCharPointer());
const String& startString = strings.getReference (start);
for (;;)
{
const int c1 = s1 < string1.end ? (int) s1.getAndAdvance() : 0;
const int c2 = (int) s2.getAndAdvance();
const int diff = c1 - c2;
if (startString == newString)
return startString.getCharPointer();
if (diff != 0) return diff < 0 ? -1 : 1;
if (c1 == 0) break;
}
const int halfway = (start + end) >> 1;
return 0;
}
if (halfway == start)
{
if (startString.compare (newString) < 0)
++start;
template <typename NewStringType>
static String addPooledString (Array<String>& strings, const NewStringType& newString)
{
int start = 0;
int end = strings.size();
strings.insert (start, newString);
return strings.getReference (start).getCharPointer();
}
while (start < end)
{
const String& startString = strings.getReference (start);
const int startComp = compareStrings (newString, startString);
const int comp = strings.getReference (halfway).compare (newString);
if (startComp == 0)
return startString;
if (comp == 0)
return strings.getReference (halfway).getCharPointer();
const int halfway = (start + end) / 2;
if (comp < 0)
start = halfway;
else
end = halfway;
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::CharPointerType StringPool::getPooledString (const String& s)
String StringPool::getPooledString (const char* const newString)
{ {
if (s.isEmpty())
return String().getCharPointer();
if (newString == nullptr || *newString == 0)
return String();
return StringPoolHelpers::getPooledStringFromArray (strings, s, lock);
const ScopedLock sl (lock);
garbageCollectIfNeeded();
return addPooledString (strings, CharPointer_UTF8 (newString));
} }
String::CharPointerType StringPool::getPooledString (const char* const s)
String StringPool::getPooledString (String::CharPointerType start, String::CharPointerType end)
{ {
if (s == nullptr || *s == 0)
return String().getCharPointer();
if (start.isEmpty() || start == end)
return String();
return StringPoolHelpers::getPooledStringFromArray (strings, s, lock);
const ScopedLock sl (lock);
garbageCollectIfNeeded();
return addPooledString (strings, StartEndString (start, end));
} }
String::CharPointerType StringPool::getPooledString (const wchar_t* const s)
String StringPool::getPooledString (const String& newString)
{ {
if (s == nullptr || *s == 0)
return String().getCharPointer();
if (newString.isEmpty())
return String();
return StringPoolHelpers::getPooledStringFromArray (strings, s, lock);
const ScopedLock sl (lock);
garbageCollectIfNeeded();
return addPooledString (strings, newString);
} }
int StringPool::size() const noexcept
void StringPool::garbageCollectIfNeeded()
{ {
return strings.size();
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();
} }
String::CharPointerType StringPool::operator[] (const int index) const noexcept
StringPool& StringPool::getGlobalPool() noexcept
{ {
return strings [index].getCharPointer();
static StringPool pool;
return pool;
} }

+ 18
- 21
modules/juce_core/text/juce_StringPool.h View File

@@ -52,40 +52,37 @@ public:
~StringPool(); ~StringPool();
//============================================================================== //==============================================================================
/** Returns a pointer to a copy of the string that is passed in.
The pool will always return the same pointer when asked for a string that matches it.
The pool will own all the pointers that it returns, deleting them when the pool itself
is deleted.
/** 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::CharPointerType getPooledString (const String& original);
String getPooledString (const String& original);
/** Returns a pointer to a copy of the string that is passed in. /** Returns a pointer to a copy of the string that is passed in.
The pool will always return the same pointer when asked for a string that matches it.
The pool will own all the pointers that it returns, deleting them when the pool itself
is deleted.
The pool will always return the same String object when asked for a string that matches it.
*/ */
String::CharPointerType getPooledString (const char* original);
String getPooledString (const char* original);
/** Returns a pointer to a copy of the string that is passed in. /** Returns a pointer to a copy of the string that is passed in.
The pool will always return the same pointer when asked for a string that matches it.
The pool will own all the pointers that it returns, deleting them when the pool itself
is deleted.
The pool will always return the same String object when asked for a string that matches it.
*/ */
String::CharPointerType getPooledString (const wchar_t* original);
String getPooledString (String::CharPointerType start, String::CharPointerType end);
//============================================================================== //==============================================================================
/** Returns the number of strings in the pool. */
int size() const noexcept;
/** 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 one of the strings in the pool, by index. */
String::CharPointerType operator[] (int index) const noexcept;
/** Returns a shared global pool which is used for things like Identifiers, XML parsing. */
static StringPool& getGlobalPool() noexcept;
private: private:
Array <String> strings;
Array<String> strings;
CriticalSection lock; CriticalSection lock;
uint32 lastGarbageCollectionTime;
void garbageCollectIfNeeded();
}; };


+ 2
- 3
modules/juce_core/xml/juce_XmlDocument.cpp View File

@@ -408,7 +408,7 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements)
} }
} }
node = new XmlElement (String (input, endOfToken));
node = new XmlElement (input, endOfToken);
input = endOfToken; input = endOfToken;
LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes); LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes);
@@ -458,8 +458,7 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements)
if (nextChar == '"' || nextChar == '\'') if (nextChar == '"' || nextChar == '\'')
{ {
XmlElement::XmlAttributeNode* const newAtt XmlElement::XmlAttributeNode* const newAtt
= new XmlElement::XmlAttributeNode (String (attNameStart, attNameEnd),
String::empty);
= new XmlElement::XmlAttributeNode (attNameStart, attNameEnd);
readQuotedString (newAtt->value); readQuotedString (newAtt->value);
attributeAppender.append (newAtt); attributeAppender.append (newAtt);


+ 39
- 14
modules/juce_core/xml/juce_XmlElement.cpp View File

@@ -32,7 +32,7 @@ XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) n
{ {
} }
XmlElement::XmlAttributeNode::XmlAttributeNode (const String& n, const String& v) noexcept
XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept
: name (n), value (v) : name (n), value (v)
{ {
#if JUCE_DEBUG #if JUCE_DEBUG
@@ -42,15 +42,16 @@ XmlElement::XmlAttributeNode::XmlAttributeNode (const String& n, const String& v
#endif #endif
} }
bool XmlElement::XmlAttributeNode::hasName (StringRef nameToMatch) const noexcept
XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
: name (nameStart, nameEnd)
{ {
return name.equalsIgnoreCase (nameToMatch);
} }
//============================================================================== //==============================================================================
XmlElement::XmlElement (const String& tag) noexcept
: tagName (tag)
static void sanityCheckTagName (const String& tag)
{ {
(void) tag;
// the tag name mustn't be empty, or it'll look like a text element! // the tag name mustn't be empty, or it'll look like a text element!
jassert (tag.containsNonWhitespaceChars()) jassert (tag.containsNonWhitespaceChars())
@@ -58,6 +59,30 @@ XmlElement::XmlElement (const String& tag) noexcept
jassert (! tag.containsAnyOf (" <>/&(){}")); jassert (! tag.containsAnyOf (" <>/&(){}"));
} }
XmlElement::XmlElement (const String& tag)
: tagName (StringPool::getGlobalPool().getPooledString (tag))
{
sanityCheckTagName (tagName);
}
XmlElement::XmlElement (const char* tag)
: tagName (StringPool::getGlobalPool().getPooledString (tag))
{
sanityCheckTagName (tagName);
}
XmlElement::XmlElement (const Identifier& tag)
: tagName (tag.toString())
{
sanityCheckTagName (tagName);
}
XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd)
: tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd))
{
sanityCheckTagName (tagName);
}
XmlElement::XmlElement (int /*dummy*/) noexcept XmlElement::XmlElement (int /*dummy*/) noexcept
{ {
} }
@@ -407,7 +432,7 @@ int XmlElement::getNumAttributes() const noexcept
const String& XmlElement::getAttributeName (const int index) const noexcept const String& XmlElement::getAttributeName (const int index) const noexcept
{ {
if (const XmlAttributeNode* const att = attributes [index]) if (const XmlAttributeNode* const att = attributes [index])
return att->name;
return att->name.toString();
return String::empty; return String::empty;
} }
@@ -423,7 +448,7 @@ const String& XmlElement::getAttributeValue (const int index) const noexcept
XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept
{ {
for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
if (att->hasName (attributeName))
if (att->name == attributeName)
return att; return att;
return nullptr; return nullptr;
@@ -495,7 +520,7 @@ bool XmlElement::compareAttribute (StringRef attributeName,
} }
//============================================================================== //==============================================================================
void XmlElement::setAttribute (const String& attributeName, const String& value)
void XmlElement::setAttribute (const Identifier& attributeName, const String& value)
{ {
if (attributes == nullptr) if (attributes == nullptr)
{ {
@@ -505,7 +530,7 @@ void XmlElement::setAttribute (const String& attributeName, const String& value)
{ {
for (XmlAttributeNode* att = attributes; ; att = att->nextListItem) for (XmlAttributeNode* att = attributes; ; att = att->nextListItem)
{ {
if (att->hasName (attributeName))
if (att->name == attributeName)
{ {
att->value = value; att->value = value;
break; break;
@@ -520,23 +545,23 @@ void XmlElement::setAttribute (const String& attributeName, const String& value)
} }
} }
void XmlElement::setAttribute (const String& attributeName, const int number)
void XmlElement::setAttribute (const Identifier& attributeName, const int number)
{ {
setAttribute (attributeName, String (number)); setAttribute (attributeName, String (number));
} }
void XmlElement::setAttribute (const String& attributeName, const double number)
void XmlElement::setAttribute (const Identifier& attributeName, const double number)
{ {
setAttribute (attributeName, String (number, 20)); setAttribute (attributeName, String (number, 20));
} }
void XmlElement::removeAttribute (const String& attributeName) noexcept
void XmlElement::removeAttribute (const Identifier& attributeName) noexcept
{ {
for (LinkedListPointer<XmlAttributeNode>* att = &attributes; for (LinkedListPointer<XmlAttributeNode>* att = &attributes;
att->get() != nullptr; att->get() != nullptr;
att = &(att->get()->nextListItem)) att = &(att->get()->nextListItem))
{ {
if (att->get()->hasName (attributeName))
if (att->get()->name == attributeName)
{ {
delete att->removeNext(); delete att->removeNext();
break; break;
@@ -615,7 +640,7 @@ void XmlElement::prependChildElement (XmlElement* newNode) noexcept
} }
} }
XmlElement* XmlElement::createNewChildElement (const String& childTagName)
XmlElement* XmlElement::createNewChildElement (StringRef childTagName)
{ {
XmlElement* const newElement = new XmlElement (childTagName); XmlElement* const newElement = new XmlElement (childTagName);
addChildElement (newElement); addChildElement (newElement);


+ 38
- 25
modules/juce_core/xml/juce_XmlElement.h View File

@@ -144,20 +144,29 @@ class JUCE_API XmlElement
public: public:
//============================================================================== //==============================================================================
/** Creates an XmlElement with this tag name. */ /** Creates an XmlElement with this tag name. */
explicit XmlElement (const String& tagName) noexcept;
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. */
XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd);
/** Creates a (deep) copy of another element. */ /** Creates a (deep) copy of another element. */
XmlElement (const XmlElement& other);
XmlElement (const XmlElement&);
/** Creates a (deep) copy of another element. */ /** Creates a (deep) copy of another element. */
XmlElement& operator= (const XmlElement& other);
XmlElement& operator= (const XmlElement&);
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
XmlElement (XmlElement&& other) noexcept;
XmlElement& operator= (XmlElement&& other) noexcept;
XmlElement (XmlElement&&) noexcept;
XmlElement& operator= (XmlElement&&) noexcept;
#endif #endif
/** Deleting an XmlElement will also delete all its child elements. */
/** Deleting an XmlElement will also delete all of its child elements. */
~XmlElement() noexcept; ~XmlElement() noexcept;
//============================================================================== //==============================================================================
@@ -303,13 +312,11 @@ public:
bool hasAttribute (StringRef attributeName) const noexcept; bool hasAttribute (StringRef attributeName) const noexcept;
/** Returns the value of a named attribute. /** Returns the value of a named attribute.
@param attributeName the name of the attribute to look up @param attributeName the name of the attribute to look up
*/ */
const String& getStringAttribute (StringRef attributeName) const noexcept; const String& getStringAttribute (StringRef attributeName) const noexcept;
/** Returns the value of a named attribute. /** Returns the value of a named attribute.
@param attributeName the name of the attribute to look up @param attributeName the name of the attribute to look up
@param defaultReturnValue a value to return if the element doesn't have an attribute @param defaultReturnValue a value to return if the element doesn't have an attribute
with this name with this name
@@ -377,7 +384,7 @@ public:
@param newValue the value to set it to @param newValue the value to set it to
@see removeAttribute @see removeAttribute
*/ */
void setAttribute (const String& attributeName, const String& newValue);
void setAttribute (const Identifier& attributeName, const String& newValue);
/** Adds a named attribute to the element, setting it to an integer value. /** Adds a named attribute to the element, setting it to an integer value.
@@ -391,7 +398,7 @@ public:
@param attributeName the name of the attribute to set @param attributeName the name of the attribute to set
@param newValue the value to set it to @param newValue the value to set it to
*/ */
void setAttribute (const String& attributeName, int newValue);
void setAttribute (const Identifier& attributeName, int newValue);
/** Adds a named attribute to the element, setting it to a floating-point value. /** Adds a named attribute to the element, setting it to a floating-point value.
@@ -405,14 +412,14 @@ public:
@param attributeName the name of the attribute to set @param attributeName the name of the attribute to set
@param newValue the value to set it to @param newValue the value to set it to
*/ */
void setAttribute (const String& attributeName, double newValue);
void setAttribute (const Identifier& attributeName, double newValue);
/** Removes a named attribute from the element. /** Removes a named attribute from the element.
@param attributeName the name of the attribute to remove @param attributeName the name of the attribute to remove
@see removeAllAttributes @see removeAllAttributes
*/ */
void removeAttribute (const String& attributeName) noexcept;
void removeAttribute (const Identifier& attributeName) noexcept;
/** Removes all attributes from this element. */ /** Removes all attributes from this element. */
void removeAllAttributes() noexcept; void removeAllAttributes() noexcept;
@@ -555,7 +562,7 @@ public:
XmlElement* newElement = myParentElement->createNewChildElement ("foobar"); XmlElement* newElement = myParentElement->createNewChildElement ("foobar");
@endcode @endcode
*/ */
XmlElement* createNewChildElement (const String& tagName);
XmlElement* createNewChildElement (StringRef tagName);
/** Replaces one of this element's children with another node. /** Replaces one of this element's children with another node.
@@ -712,25 +719,26 @@ private:
struct XmlAttributeNode struct XmlAttributeNode
{ {
XmlAttributeNode (const XmlAttributeNode&) noexcept; XmlAttributeNode (const XmlAttributeNode&) noexcept;
XmlAttributeNode (const String& name, const String& value) noexcept;
XmlAttributeNode (const Identifier&, const String&) noexcept;
XmlAttributeNode (String::CharPointerType, String::CharPointerType);
LinkedListPointer<XmlAttributeNode> nextListItem; LinkedListPointer<XmlAttributeNode> nextListItem;
String name, value;
bool hasName (StringRef) const noexcept;
Identifier name;
String value;
private: private:
XmlAttributeNode& operator= (const XmlAttributeNode&);
XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION;
}; };
friend class XmlDocument; friend class XmlDocument;
friend class LinkedListPointer <XmlAttributeNode>;
friend class LinkedListPointer <XmlElement>;
friend class LinkedListPointer <XmlElement>::Appender;
LinkedListPointer <XmlElement> nextListItem;
LinkedListPointer <XmlElement> firstChildElement;
LinkedListPointer <XmlAttributeNode> attributes;
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; String tagName;
XmlElement (int) noexcept; XmlElement (int) noexcept;
@@ -740,6 +748,11 @@ private:
void reorderChildElements (XmlElement**, int) noexcept; void reorderChildElements (XmlElement**, int) noexcept;
XmlAttributeNode* getAttribute (StringRef) const 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) JUCE_LEAK_DETECTOR (XmlElement)
}; };


+ 1
- 1
modules/juce_data_structures/values/juce_ValueTree.cpp View File

@@ -409,7 +409,7 @@ public:
XmlElement* createXml() const XmlElement* createXml() const
{ {
XmlElement* const xml = new XmlElement (type.toString());
XmlElement* const xml = new XmlElement (type);
properties.copyToXmlAttributes (*xml); properties.copyToXmlAttributes (*xml);
// (NB: it's faster to add nodes to XML elements in reverse order) // (NB: it's faster to add nodes to XML elements in reverse order)


Loading…
Cancel
Save