Add String::startsWith() and endsWith() for string literals
authorbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 30 Apr 2012 21:32:44 +0000 (21:32 +0000)
committerbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 30 Apr 2012 21:32:44 +0000 (21:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=85154

Reviewed by Darin Adler.

Source/WebCore:

Update WebCore to use the simpler startsWith() and endsWith() taking
a UChar.

* css/CSSParser.cpp:
(WebCore::CSSParser::markPropertyEnd):
* css/WebKitCSSKeyframeRule.cpp:
(WebCore::StyleKeyframe::parseKeyString):
* editing/markup.cpp:
(WebCore::createFragmentFromText):
* html/HTMLObjectElement.cpp:
(WebCore::HTMLObjectElement::addSubresourceAttributeURLs):
* html/HTMLTextFormControlElement.cpp:
(WebCore::HTMLTextFormControlElement::setInnerTextValue):
* inspector/ContentSearchUtils.cpp:
(WebCore::ContentSearchUtils::getRegularExpressionMatchesByLines):
* inspector/InspectorCSSAgent.cpp:
(WebCore::InspectorCSSAgent::SetPropertyTextAction::redo):
* loader/MainResourceLoader.cpp:
(WebCore::MainResourceLoader::substituteMIMETypeFromPluginDatabase):
* loader/appcache/ManifestParser.cpp:
(WebCore::parseManifest):
* platform/blackberry/CookieManager.cpp:
(WebCore::CookieManager::shouldRejectForSecurityReason):
* platform/posix/FileSystemPOSIX.cpp:
(WebCore::pathByAppendingComponent):
* plugins/PluginDatabase.cpp:
(WebCore::PluginDatabase::findPlugin):
* svg/SVGStopElement.cpp:
(WebCore::SVGStopElement::parseAttribute):
* svg/animation/SVGSMILElement.cpp:
(WebCore::SVGSMILElement::parseOffsetValue):
(WebCore::SVGSMILElement::parseCondition):

Source/WebKit/blackberry:

* WebKitSupport/DOMSupport.cpp:
(BlackBerry::WebKit::DOMSupport::elementPatternMatches):

Source/WebKit2:

Update WebKit2 to use String::endsWith(UChar).

* UIProcess/Plugins/PluginInfoStore.cpp:
(WebKit::pathExtension):

Source/WTF:

When invoking StringImpl::startsWidth() or StringImpl::endsWith() with
a string literal, a new String was constructed implicitly, allocating
a new StringImpl and copying the characters for the operation.

This patch adds a version of those methods for single characters and
string literals.
This allows us to avoid allocating memory and use the characters in place,
and it permits some extra shortcuts in the implementation.

* wtf/text/AtomicString.h:
(WTF::AtomicString::startsWith):
(AtomicString):
(WTF::AtomicString::endsWith):
* wtf/text/StringImpl.cpp:
(WTF::equalInner):
(WTF):
(WTF::StringImpl::startsWith):
(WTF::StringImpl::endsWith):
* wtf/text/StringImpl.h:
(WTF::StringImpl::startsWith):
(StringImpl):
(WTF::StringImpl::endsWith):
* wtf/text/WTFString.h:
(WTF::String::startsWith):
(String):
(WTF::String::endsWith):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@115669 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
Source/WTF/ChangeLog
Source/WTF/wtf/text/AtomicString.h
Source/WTF/wtf/text/StringImpl.cpp
Source/WTF/wtf/text/StringImpl.h
Source/WTF/wtf/text/WTFString.h
Source/WebCore/ChangeLog
Source/WebCore/css/CSSParser.cpp
Source/WebCore/css/WebKitCSSKeyframeRule.cpp
Source/WebCore/editing/markup.cpp
Source/WebCore/html/HTMLObjectElement.cpp
Source/WebCore/html/HTMLTextFormControlElement.cpp
Source/WebCore/inspector/ContentSearchUtils.cpp
Source/WebCore/inspector/InspectorCSSAgent.cpp
Source/WebCore/loader/MainResourceLoader.cpp
Source/WebCore/loader/appcache/ManifestParser.cpp
Source/WebCore/platform/blackberry/CookieManager.cpp
Source/WebCore/platform/posix/FileSystemPOSIX.cpp
Source/WebCore/plugins/PluginDatabase.cpp
Source/WebCore/svg/SVGStopElement.cpp
Source/WebCore/svg/animation/SVGSMILElement.cpp
Source/WebKit/blackberry/ChangeLog
Source/WebKit/blackberry/WebKitSupport/DOMSupport.cpp
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/Plugins/PluginInfoStore.cpp

index 47c4c39..ed01d8d 100644 (file)
@@ -1,3 +1,37 @@
+2012-04-30  Benjamin Poulain  <benjamin@webkit.org>
+
+        Add String::startsWith() and endsWith() for string literals
+        https://bugs.webkit.org/show_bug.cgi?id=85154
+
+        Reviewed by Darin Adler.
+
+        When invoking StringImpl::startsWidth() or StringImpl::endsWith() with
+        a string literal, a new String was constructed implicitly, allocating
+        a new StringImpl and copying the characters for the operation.
+
+        This patch adds a version of those methods for single characters and
+        string literals.
+        This allows us to avoid allocating memory and use the characters in place,
+        and it permits some extra shortcuts in the implementation.
+
+        * wtf/text/AtomicString.h:
+        (WTF::AtomicString::startsWith):
+        (AtomicString):
+        (WTF::AtomicString::endsWith):
+        * wtf/text/StringImpl.cpp:
+        (WTF::equalInner):
+        (WTF):
+        (WTF::StringImpl::startsWith):
+        (WTF::StringImpl::endsWith):
+        * wtf/text/StringImpl.h:
+        (WTF::StringImpl::startsWith):
+        (StringImpl):
+        (WTF::StringImpl::endsWith):
+        * wtf/text/WTFString.h:
+        (WTF::String::startsWith):
+        (String):
+        (WTF::String::endsWith):
+
 2012-04-30  Anders Carlsson  <andersca@apple.com>
 
         WTF::bind should work with blocks
index e7323f9..ca133a5 100644 (file)
@@ -81,8 +81,19 @@ public:
     
     bool startsWith(const String& s, bool caseSensitive = true) const
         { return m_string.startsWith(s, caseSensitive); }
+    bool startsWith(UChar character) const
+        { return m_string.startsWith(character); }
+    template<unsigned matchLength>
+    bool startsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const
+        { return m_string.startsWith<matchLength>(prefix, caseSensitive); }
+
     bool endsWith(const String& s, bool caseSensitive = true) const
         { return m_string.endsWith(s, caseSensitive); }
+    bool endsWith(UChar character) const
+        { return m_string.endsWith(character); }
+    template<unsigned matchLength>
+    bool endsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const
+        { return m_string.endsWith<matchLength>(prefix, caseSensitive); }
     
     WTF_EXPORT_PRIVATE AtomicString lower() const;
     AtomicString upper() const { return AtomicString(impl()->upper()); }
index 9106088..372db17 100644 (file)
@@ -1095,6 +1095,35 @@ size_t StringImpl::reverseFindIgnoringCase(StringImpl* matchString, unsigned ind
     return delta;
 }
 
+ALWAYS_INLINE static bool equalInner(const StringImpl* stringImpl, unsigned startOffset, const char* matchString, unsigned matchLength, bool caseSensitive)
+{
+    ASSERT(stringImpl);
+    ASSERT(matchLength <= stringImpl->length());
+    ASSERT(startOffset + matchLength <= stringImpl->length());
+
+    if (caseSensitive) {
+        if (stringImpl->is8Bit())
+            return equal(stringImpl->characters8() + startOffset, reinterpret_cast<const LChar*>(matchString), matchLength);
+        return equal(stringImpl->characters16() + startOffset, reinterpret_cast<const LChar*>(matchString), matchLength);
+    }
+    if (stringImpl->is8Bit())
+        return equalIgnoringCase(stringImpl->characters8() + startOffset, reinterpret_cast<const LChar*>(matchString), matchLength);
+    return equalIgnoringCase(stringImpl->characters16() + startOffset, reinterpret_cast<const LChar*>(matchString), matchLength);
+}
+
+bool StringImpl::startsWith(UChar character) const
+{
+    return m_length && (*this)[0] == character;
+}
+
+bool StringImpl::startsWith(const char* matchString, unsigned matchLength, bool caseSensitive) const
+{
+    ASSERT(matchLength);
+    if (matchLength > length())
+        return false;
+    return equalInner(this, 0, matchString, matchLength, caseSensitive);
+}
+
 bool StringImpl::endsWith(StringImpl* matchString, bool caseSensitive)
 {
     ASSERT(matchString);
@@ -1105,6 +1134,20 @@ bool StringImpl::endsWith(StringImpl* matchString, bool caseSensitive)
     return false;
 }
 
+bool StringImpl::endsWith(UChar character) const
+{
+    return m_length && (*this)[m_length - 1] == character;
+}
+
+bool StringImpl::endsWith(const char* matchString, unsigned matchLength, bool caseSensitive) const
+{
+    ASSERT(matchLength);
+    if (matchLength > length())
+        return false;
+    unsigned startOffset = length() - matchLength;
+    return equalInner(this, startOffset, matchString, matchLength, caseSensitive);
+}
+
 PassRefPtr<StringImpl> StringImpl::replace(UChar oldC, UChar newC)
 {
     if (oldC == newC)
index ce0e469..3734360 100644 (file)
@@ -498,7 +498,16 @@ public:
     WTF_EXPORT_PRIVATE size_t reverseFindIgnoringCase(StringImpl*, unsigned index = UINT_MAX);
 
     bool startsWith(StringImpl* str, bool caseSensitive = true) { return (caseSensitive ? reverseFind(str, 0) : reverseFindIgnoringCase(str, 0)) == 0; }
+    WTF_EXPORT_PRIVATE bool startsWith(UChar) const;
+    WTF_EXPORT_PRIVATE bool startsWith(const char*, unsigned matchLength, bool caseSensitive) const;
+    template<unsigned matchLength>
+    bool startsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const { return startsWith(prefix, matchLength - 1, caseSensitive); };
+
     WTF_EXPORT_PRIVATE bool endsWith(StringImpl*, bool caseSensitive = true);
+    WTF_EXPORT_PRIVATE bool endsWith(UChar) const;
+    WTF_EXPORT_PRIVATE bool endsWith(const char*, unsigned matchLength, bool caseSensitive) const;
+    template<unsigned matchLength>
+    bool endsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const { return endsWith(prefix, matchLength - 1, caseSensitive); }
 
     WTF_EXPORT_PRIVATE PassRefPtr<StringImpl> replace(UChar, UChar);
     WTF_EXPORT_PRIVATE PassRefPtr<StringImpl> replace(UChar, StringImpl*);
index 2ca2830..5e1763a 100644 (file)
@@ -248,8 +248,19 @@ public:
 
     bool startsWith(const String& s, bool caseSensitive = true) const
         { return m_impl ? m_impl->startsWith(s.impl(), caseSensitive) : s.isEmpty(); }
+    bool startsWith(UChar character) const
+        { return m_impl ? m_impl->startsWith(character) : false; }
+    template<unsigned matchLength>
+    bool startsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const
+        { return m_impl ? m_impl->startsWith<matchLength>(prefix, caseSensitive) : !matchLength; }
+
     bool endsWith(const String& s, bool caseSensitive = true) const
         { return m_impl ? m_impl->endsWith(s.impl(), caseSensitive) : s.isEmpty(); }
+    bool endsWith(UChar character) const
+        { return m_impl ? m_impl->endsWith(character) : false; }
+    template<unsigned matchLength>
+    bool endsWith(const char (&prefix)[matchLength], bool caseSensitive = true) const
+        { return m_impl ? m_impl->endsWith<matchLength>(prefix, caseSensitive) : !matchLength; }
 
     WTF_EXPORT_PRIVATE void append(const String&);
     WTF_EXPORT_PRIVATE void append(LChar);
index ecca44e..8e63881 100644 (file)
@@ -1,3 +1,43 @@
+2012-04-30  Benjamin Poulain  <benjamin@webkit.org>
+
+        Add String::startsWith() and endsWith() for string literals
+        https://bugs.webkit.org/show_bug.cgi?id=85154
+
+        Reviewed by Darin Adler.
+
+        Update WebCore to use the simpler startsWith() and endsWith() taking
+        a UChar.
+
+        * css/CSSParser.cpp:
+        (WebCore::CSSParser::markPropertyEnd):
+        * css/WebKitCSSKeyframeRule.cpp:
+        (WebCore::StyleKeyframe::parseKeyString):
+        * editing/markup.cpp:
+        (WebCore::createFragmentFromText):
+        * html/HTMLObjectElement.cpp:
+        (WebCore::HTMLObjectElement::addSubresourceAttributeURLs):
+        * html/HTMLTextFormControlElement.cpp:
+        (WebCore::HTMLTextFormControlElement::setInnerTextValue):
+        * inspector/ContentSearchUtils.cpp:
+        (WebCore::ContentSearchUtils::getRegularExpressionMatchesByLines):
+        * inspector/InspectorCSSAgent.cpp:
+        (WebCore::InspectorCSSAgent::SetPropertyTextAction::redo):
+        * loader/MainResourceLoader.cpp:
+        (WebCore::MainResourceLoader::substituteMIMETypeFromPluginDatabase):
+        * loader/appcache/ManifestParser.cpp:
+        (WebCore::parseManifest):
+        * platform/blackberry/CookieManager.cpp:
+        (WebCore::CookieManager::shouldRejectForSecurityReason):
+        * platform/posix/FileSystemPOSIX.cpp:
+        (WebCore::pathByAppendingComponent):
+        * plugins/PluginDatabase.cpp:
+        (WebCore::PluginDatabase::findPlugin):
+        * svg/SVGStopElement.cpp:
+        (WebCore::SVGStopElement::parseAttribute):
+        * svg/animation/SVGSMILElement.cpp:
+        (WebCore::SVGSMILElement::parseOffsetValue):
+        (WebCore::SVGSMILElement::parseCondition):
+
 2012-04-30  Abhishek Arya  <inferno@chromium.org>
 
         Remove positioned float code.
index cf07357..05a361a 100644 (file)
@@ -9310,9 +9310,9 @@ void CSSParser::markPropertyEnd(bool isImportantFound, bool isPropertyParsed)
         const unsigned end = m_propertyRange.end;
         ASSERT(start < end);
         String propertyString = String(m_dataStart.get() + start, end - start).stripWhiteSpace();
-        if (propertyString.endsWith(";", true))
+        if (propertyString.endsWith(';'))
             propertyString = propertyString.left(propertyString.length() - 1);
-        size_t colonIndex = propertyString.find(":");
+        size_t colonIndex = propertyString.find(':');
         ASSERT(colonIndex != notFound);
 
         String name = propertyString.left(colonIndex).stripWhiteSpace();
index 9b38a37..7692086 100644 (file)
@@ -53,7 +53,7 @@ void StyleKeyframe::parseKeyString(const String& s, Vector<float>& keys)
             key = 0;
         else if (cur == "to")
             key = 1;
-        else if (cur.endsWith("%")) {
+        else if (cur.endsWith('%')) {
             float k = cur.substring(0, cur.length() - 1).toFloat();
             if (k >= 0 && k <= 100)
                 key = k/100;
index 9515b90..bb49b37 100644 (file)
@@ -859,7 +859,7 @@ PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String
     if (renderer && renderer->style()->preserveNewline()) {
         fragment->appendChild(document->createTextNode(string), ec);
         ASSERT(!ec);
-        if (string.endsWith("\n")) {
+        if (string.endsWith('\n')) {
             RefPtr<Element> element = createBreakElement(document);
             element->setAttribute(classAttr, AppleInterchangeNewline);            
             fragment->appendChild(element.release(), ec);
index a8d34b9..e09349c 100644 (file)
@@ -476,7 +476,7 @@ void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) con
     // FIXME: Passing a string that starts with "#" to the completeURL function does
     // not seem like it would work. The image element has similar but not identical code.
     const AtomicString& useMap = getAttribute(usemapAttr);
-    if (useMap.startsWith("#"))
+    if (useMap.startsWith('#'))
         addSubresourceURL(urls, document()->completeURL(useMap));
 }
 
index 76c0abe..6a6248c 100644 (file)
@@ -473,7 +473,7 @@ void HTMLTextFormControlElement::setInnerTextValue(const String& value)
         innerTextElement()->setInnerText(value, ec);
         ASSERT(!ec);
 
-        if (value.endsWith("\n") || value.endsWith("\r")) {
+        if (value.endsWith('\n') || value.endsWith('\r')) {
             innerTextElement()->appendChild(HTMLBRElement::create(document()), ec);
             ASSERT(!ec);
         }
index afa5239..b94b3bb 100644 (file)
@@ -81,7 +81,7 @@ static Vector<pair<int, String> > getRegularExpressionMatchesByLines(const Regul
         String line = text.substring(start, lineEnd - start);
         if (line.endsWith("\r\n"))
             line = line.left(line.length() - 2);
-        if (line.endsWith("\n"))
+        if (line.endsWith('\n'))
             line = line.left(line.length() - 1);
 
         int matchLength;
index 9e9b56e..1e0852b 100644 (file)
@@ -321,7 +321,7 @@ public:
         bool result = m_styleSheet->setPropertyText(m_cssId, m_propertyIndex, m_text, m_overwrite, &oldText, ec);
         m_oldText = oldText.stripWhiteSpace();
         // FIXME: remove this once the model handles this case.
-        if (!m_oldText.endsWith(";"))
+        if (!m_oldText.endsWith(';'))
             m_oldText += ";";
         return result;
     }
index 3a57615..4446384 100644 (file)
@@ -342,7 +342,7 @@ void MainResourceLoader::substituteMIMETypeFromPluginDatabase(const ResourceResp
         return;
 
     String filename = r.url().lastPathComponent();
-    if (filename.endsWith("/"))
+    if (filename.endsWith('/'))
         return;
 
     size_t extensionPos = filename.reverseFind('.');
index 8a589eb..b0f6bc8 100644 (file)
@@ -96,7 +96,7 @@ bool parseManifest(const KURL& manifestURL, const char* data, int length, Manife
             mode = Fallback;
         else if (line == "NETWORK:")
             mode = OnlineWhitelist;
-        else if (line.endsWith(":"))
+        else if (line.endsWith(':'))
             mode = Unknown;
         else if (mode == Unknown)
             continue;
index bf63b61..dd72e67 100644 (file)
@@ -184,7 +184,7 @@ bool CookieManager::shouldRejectForSecurityReason(const ParsedCookie* cookie, co
     // a.b.com matches b.com, a.b.com matches .B.com and a.b.com matches .A.b.Com
     // and so on.
     String hostDomainName = url.host();
-    hostDomainName = hostDomainName.startsWith(".") ? hostDomainName : "." + hostDomainName;
+    hostDomainName = hostDomainName.startsWith('.') ? hostDomainName : "." + hostDomainName;
     if (!hostDomainName.endsWith(cookie->domain(), false)) {
         LOG_ERROR("Cookie %s is rejected because its domain does not domain match the URL %s\n", cookie->toString().utf8().data(), url.string().utf8().data());
         return true;
index 2bf6365..fda8126 100644 (file)
@@ -183,10 +183,9 @@ bool getFileModificationTime(const String& path, time_t& result)
 
 String pathByAppendingComponent(const String& path, const String& component)
 {
-    if (path.endsWith("/"))
+    if (path.endsWith('/'))
         return path + component;
-    else
-        return path + "/" + component;
+    return path + "/" + component;
 }
 
 bool makeAllDirectories(const String& path)
index 9300782..9508c5b 100644 (file)
@@ -283,7 +283,7 @@ PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType)
         return pluginForMIMEType(mimeType);
     
     String filename = url.lastPathComponent();
-    if (filename.endsWith("/"))
+    if (filename.endsWith('/'))
         return 0;
     
     int extensionPos = filename.reverseFind('.');
index 7b973a3..4818b85 100644 (file)
@@ -71,7 +71,7 @@ void SVGStopElement::parseAttribute(Attribute* attr)
 
     if (attr->name() == SVGNames::offsetAttr) {
         const String& value = attr->value();
-        if (value.endsWith("%"))
+        if (value.endsWith('%'))
             setOffsetBaseValue(value.left(value.length() - 1).toFloat() / 100.0f);
         else
             setOffsetBaseValue(value.toFloat());
index 78c7937..d3e9642 100644 (file)
@@ -248,13 +248,13 @@ SMILTime SVGSMILElement::parseOffsetValue(const String& data)
     bool ok;
     double result = 0;
     String parse = data.stripWhiteSpace();
-    if (parse.endsWith("h"))
+    if (parse.endsWith('h'))
         result = parse.left(parse.length() - 1).toDouble(&ok) * 60 * 60;
     else if (parse.endsWith("min"))
         result = parse.left(parse.length() - 3).toDouble(&ok) * 60;
     else if (parse.endsWith("ms"))
         result = parse.left(parse.length() - 2).toDouble(&ok) / 1000;
-    else if (parse.endsWith("s"))
+    else if (parse.endsWith('s'))
         result = parse.left(parse.length() - 1).toDouble(&ok);
     else
         result = parse.toDouble(&ok);
@@ -345,7 +345,7 @@ bool SVGSMILElement::parseCondition(const String& value, BeginOrEnd beginOrEnd)
 
     Condition::Type type;
     int repeats = -1;
-    if (nameString.startsWith("repeat(") && nameString.endsWith(")")) {
+    if (nameString.startsWith("repeat(") && nameString.endsWith(')')) {
         // FIXME: For repeat events we just need to add the data carrying TimeEvent class and 
         // fire the events at appropiate times.
         repeats = nameString.substring(7, nameString.length() - 8).toUIntStrict(&ok);
index b381292..61de1a9 100644 (file)
@@ -1,3 +1,13 @@
+2012-04-30  Benjamin Poulain  <benjamin@webkit.org>
+
+        Add String::startsWith() and endsWith() for string literals
+        https://bugs.webkit.org/show_bug.cgi?id=85154
+
+        Reviewed by Darin Adler.
+
+        * WebKitSupport/DOMSupport.cpp:
+        (BlackBerry::WebKit::DOMSupport::elementPatternMatches):
+
 2012-04-27  Jacky Jiang  <zhajiang@rim.com>
 
         [BlackBerry] Double tap zooming does nothing on table element on bustedtees.com
index 89c9888..3b972d6 100644 (file)
@@ -424,7 +424,7 @@ bool elementPatternMatches(const char* pattern, const HTMLInputElement* inputEle
                 return true;
 
             // Is the regex specifying a character count?
-            if (patternAttribute[patternString.length()] != '{' || !patternAttribute.endsWith("}"))
+            if (patternAttribute[patternString.length()] != '{' || !patternAttribute.endsWith('}'))
                 return false;
 
             // Make sure the number in the regex is actually a number.
index 0020780..6b50f40 100644 (file)
@@ -1,3 +1,15 @@
+2012-04-30  Benjamin Poulain  <benjamin@webkit.org>
+
+        Add String::startsWith() and endsWith() for string literals
+        https://bugs.webkit.org/show_bug.cgi?id=85154
+
+        Reviewed by Darin Adler.
+
+        Update WebKit2 to use String::endsWith(UChar).
+
+        * UIProcess/Plugins/PluginInfoStore.cpp:
+        (WebKit::pathExtension):
+
 2012-04-30  Alexey Proskuryakov  <ap@apple.com>
 
         Validate keypress command names
index f37099f..cef14dc 100644 (file)
@@ -164,7 +164,7 @@ static inline String pathExtension(const KURL& url)
 {
     String extension;
     String filename = url.lastPathComponent();
-    if (!filename.endsWith("/")) {
+    if (!filename.endsWith('/')) {
         int extensionPos = filename.reverseFind('.');
         if (extensionPos != -1)
             extension = filename.substring(extensionPos + 1);