diff --git a/applications/test/fileName/Test-fileName.C b/applications/test/fileName/Test-fileName.C
index de8751c7b01dfa4f5526bd42edf832d9311c70dd..18f89b6db046d049aa071dc6ada3a044cc2cb9d2 100644
--- a/applications/test/fileName/Test-fileName.C
+++ b/applications/test/fileName/Test-fileName.C
@@ -37,6 +37,7 @@ Description
 #include "IOstreams.H"
 #include "OSspecific.H"
 #include "POSIX.H"
+#include "Switch.H"
 #include "etcFiles.H"
 
 using namespace Foam;
@@ -47,6 +48,7 @@ using namespace Foam;
 int main(int argc, char *argv[])
 {
     argList::noParallel();
+    argList::addBoolOption("ext", "test handing of file extensions");
     argList::addBoolOption("construct", "test constructors");
     argList::addBoolOption("default", "reinstate default tests");
     argList::addNote("runs default tests or specified ones only");
@@ -108,6 +110,131 @@ int main(int argc, char *argv[])
     }
 
 
+    // Test various ext() methods
+    if (args.optionFound("ext"))
+    {
+        Info<<nl << nl << "handling of fileName extension" << nl;
+
+        fileName empty;
+        fileName endWithDot("some.path/name.");
+        fileName endWithSlash("some.path/");
+        fileName input0("some.file/with.out/extension");
+        fileName input1("path.to/media/image.png");
+
+        Info<<"File : " << input0 << " ext: "
+            << Switch(input0.hasExt())
+            <<  " = " << input0.ext() << nl;
+        Info<<"File : " << input1 << " ext: "
+            << Switch(input1.hasExt())
+            <<  " = " << input1.ext() << nl;
+        Info<<"File : " << endWithDot << " ext: "
+            << Switch(endWithDot.hasExt())
+            <<  " = " << endWithDot.ext() << " <-- perhaps return false?" << nl;
+        Info<<"File : " << endWithSlash << " ext: "
+            << Switch(endWithSlash.hasExt())
+            <<  " = " << endWithSlash.ext() << nl;
+
+
+        Info<<"Remove extension " << (input0.removeExt());
+        Info<< "  now: " << input0 << nl;
+
+        Info<<"Remove extension " << (input1.removeExt());
+        Info<< "  now: " << input1 << nl;
+
+        Info<<"Remove extension " << (endWithSlash.removeExt());
+        Info<< "  now: " << endWithSlash << nl;
+
+        wordList exts{ "jpg", "png", "txt", word::null };
+        Info<<"Add extension(s): " << input1 << nl;
+        for (const word& e : exts)
+        {
+            Info<<"<" << e << ">  -> " << input1.ext(e) << nl;
+        }
+        Info<< nl;
+
+
+        Info<<"Test hasExt(word)" << nl
+            <<"~~~~~~~~~~~~~~~~~" << nl;
+        Info<<"Has extension(s):" << nl
+            << "input: " << input1 << nl;
+        for (const word& e : exts)
+        {
+            Info<<"  '" << e << "'  -> "
+                << Switch(input1.hasExt(e)) << nl;
+        }
+        Info<< nl;
+
+        Info<<"Has extension(s):" << nl
+            << "input: " << endWithDot << nl;
+        for (const word& e : exts)
+        {
+            Info<<"  '" << e << "'  -> "
+                << Switch(endWithDot.hasExt(e)) << nl;
+        }
+        Info<< nl;
+
+
+        Info<<"Test hasExt(wordRe)" << nl
+            <<"~~~~~~~~~~~~~~~~~~~" << nl;
+
+        // A regex with a zero length matcher doesn't work at all:
+        // eg "(png|jpg|txt|)" regex matcher itself
+
+        wordRe matcher0("()", wordRe::REGEXP);
+        wordRe matcher1("(png|jpg|txt)", wordRe::REGEXP);
+        wordRe matcher2("(png|txt)", wordRe::REGEXP);
+
+        Info<<"Has extension(s):" << nl
+            << "input: " << endWithDot << nl;
+        Info<<"    " << matcher0 << "  -> "
+            << Switch(endWithDot.hasExt(matcher0)) << nl;
+        Info<<"    " << matcher1 << "  -> "
+            << Switch(endWithDot.hasExt(matcher1)) << nl;
+        Info<<"    " << matcher2 << "  -> "
+            << Switch(endWithDot.hasExt(matcher2)) << nl;
+
+        Info<< "input: " << input1 << nl;
+        Info<<"    " << matcher0 << "  -> "
+            << Switch(input1.hasExt(matcher0)) << nl;
+        Info<<"    " << matcher1 << "  -> "
+            << Switch(input1.hasExt(matcher1)) << nl;
+        Info<<"    " << matcher2 << "  -> "
+            << Switch(input1.hasExt(matcher2)) << nl;
+        Info<< nl;
+
+        Info<<"Remove extension(s):" << nl << "input: " << input1 << nl;
+        while (!input1.empty())
+        {
+            if (input1.removeExt())
+            {
+                Info<< "   -> " << input1 << nl;
+            }
+            else
+            {
+                Info<< "stop> " << input1 << nl;
+                break;
+            }
+        }
+        Info<< nl;
+
+        input0.clear();
+        Info<<"test with zero-sized: " << input0 << nl;
+        Info<<"add extension: " << input0.ext("abc") << nl;
+        Info<< nl;
+
+        input0 = "this/";
+        Info<<"test add after slash: " << input0 << nl;
+        Info<<"add extension: " << input0.ext("abc")
+            << " <-- avoids accidentally creating hidden files" << nl;
+        Info<< nl;
+
+        input0 = "this.file.";
+        Info<<"test after dot: " << input0 << nl;
+        Info<<"add extension: " << input0.ext("abc")
+            << " <-- No check for repeated dots (user error!)" << nl;
+        Info<< nl;
+    }
+
     if (!defaultTests)
     {
         return 0;
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileName.C b/src/OpenFOAM/primitives/strings/fileName/fileName.C
index 7110bdf2f622d515ff960ae403a0c2e3d4c3dda8..70e925ea010e13c9beed06e77470afcc3c925b0a 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileName.C
+++ b/src/OpenFOAM/primitives/strings/fileName/fileName.C
@@ -27,6 +27,7 @@ License
 #include "wordList.H"
 #include "DynamicList.H"
 #include "OSspecific.H"
+#include "wordRe.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -303,9 +304,9 @@ Foam::fileName Foam::fileName::path() const
 
 Foam::fileName Foam::fileName::lessExt() const
 {
-    size_type i = find_last_of("./");
+    size_type i = find_ext();
 
-    if (i == npos || i == 0 || operator[](i) == '/')
+    if (i == npos)
     {
         return *this;
     }
@@ -318,9 +319,9 @@ Foam::fileName Foam::fileName::lessExt() const
 
 Foam::word Foam::fileName::ext() const
 {
-    size_type i = find_last_of("./");
+    size_type i = find_ext();
 
-    if (i == npos || i == 0 || operator[](i) == '/')
+    if (i == npos)
     {
         return word::null;
     }
@@ -331,6 +332,71 @@ Foam::word Foam::fileName::ext() const
 }
 
 
+Foam::fileName& Foam::fileName::ext(const word& ending)
+{
+    if (!ending.empty() && !empty() && operator[](size()-1) != '/')
+    {
+        append(".");
+        append(ending);
+    }
+
+    return *this;
+}
+
+
+bool Foam::fileName::hasExt() const
+{
+    return (find_ext() != npos);
+}
+
+
+bool Foam::fileName::hasExt(const word& ending) const
+{
+    size_type i = find_ext();
+    if (i == npos)
+    {
+        return false;
+    }
+
+    ++i; // Do next comparison *after* the dot
+    return
+    (
+        // Lengths must match
+        ((size() - i) == ending.size())
+     && !compare(i, npos, ending)
+    );
+}
+
+
+bool Foam::fileName::hasExt(const wordRe& ending) const
+{
+    size_type i = find_ext();
+    if (i == npos)
+    {
+        return false;
+    }
+
+    std::string end = substr(i+1, npos);
+    return ending.match(end);
+}
+
+
+bool Foam::fileName::removeExt()
+{
+    const size_type i = find_ext();
+
+    if (i == npos)
+    {
+        return false;
+    }
+    else
+    {
+        this->resize(i);
+        return true;
+    }
+}
+
+
 Foam::wordList Foam::fileName::components(const char delimiter) const
 {
     DynamicList<word> wrdList(20);
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileName.H b/src/OpenFOAM/primitives/strings/fileName/fileName.H
index cc49f365e4f0c6a6234b32a28fa062abdb6f729e..4195077d819587ccd59d73d475fe4c1b19b905fc 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileName.H
+++ b/src/OpenFOAM/primitives/strings/fileName/fileName.H
@@ -57,6 +57,7 @@ template<class T> class UList;
 typedef List<word> wordList;
 
 // Forward declaration of friend functions and operators
+class wordRe;
 class fileName;
 
 Istream& operator>>(Istream&, fileName&);
@@ -73,6 +74,10 @@ class fileName
 {
     // Private Member Functions
 
+        //- Find position of the file extension dot, return npos on failure.
+        //  A wrapped version of find_last_of("./") with additional logic.
+        inline size_type find_ext() const;
+
         //- Strip invalid characters
         inline void stripInvalid();
 
@@ -104,35 +109,35 @@ public:
         inline fileName();
 
         //- Construct as copy
-        inline fileName(const fileName&);
+        inline fileName(const fileName& fn);
 
         //- Construct as copy of word
-        inline fileName(const word&);
+        inline fileName(const word& s);
 
         //- Construct as copy of string
-        inline fileName(const string&, const bool doStripInvalid=true);
+        inline fileName(const string& s, const bool doStripInvalid=true);
 
         //- Construct as copy of std::string
-        inline fileName(const std::string&, const bool doStripInvalid=true);
+        inline fileName(const std::string& s, const bool doStripInvalid=true);
 
         //- Construct as copy of character array
-        inline fileName(const char*, const bool doStripInvalid=true);
+        inline fileName(const char* s, const bool doStripInvalid=true);
 
         //- Construct by concatenating elements of wordList separated by '/'
-        explicit fileName(const UList<word>&);
+        explicit fileName(const UList<word>& lst);
 
         //- Construct by concatenating words separated by '/'
-        explicit fileName(std::initializer_list<word>);
+        explicit fileName(std::initializer_list<word> lst);
 
 
         //- Construct from Istream
-        fileName(Istream&);
+        fileName(Istream& is);
 
 
     // Member functions
 
         //- Is this character valid for a fileName?
-        inline static bool valid(char);
+        inline static bool valid(char c);
 
         //- Cleanup file name
         //
@@ -209,6 +214,24 @@ public:
             //- Return file name extension (part after last .)
             word ext() const;
 
+            //- Append a '.' and the ending, and return the object.
+            //  The '.' and ending will not be added when the ending is empty,
+            //  or when the file name is empty or ended with a '/'.
+            fileName& ext(const word& ending);
+
+            //- Return true if it has an extension or simply ends with a '.'
+            bool hasExt() const;
+
+            //- Return true if the extension is the same as the given ending.
+            bool hasExt(const word& ending) const;
+
+            //- Return true if the extension matches the given ending.
+            bool hasExt(const wordRe& ending) const;
+
+            //- Remove extension, returning true if string changed.
+            bool removeExt();
+
+
             //- Return path components as wordList
             //
             //  Behaviour:
@@ -219,32 +242,46 @@ public:
             //    "foo/bar"       2("foo", "bar")
             //    "/foo/bar"      2("foo", "bar")
             //    "/foo/bar/"     2("foo", "bar")
-            wordList components(const char delimiter='/') const;
+            wordList components(const char delimiter = '/') const;
 
             //- Return a single component of the path
-            word component(const size_type, const char delimiter='/') const;
+            word component
+            (
+                const size_type cmpt,
+                const char delimiter = '/'
+            ) const;
 
 
     // Member operators
 
-        // Assignment
+      // Assignment
+
+        //- Copy, no character validation required
+        void operator=(const fileName& str);
+
+        //- Copy, no character validation required
+        void operator=(const word& str);
+
+        //- Copy, stripping invalid characters
+        void operator=(const string& str);
+
+        //- Copy, stripping invalid characters
+        void operator=(const std::string& str);
 
-            void operator=(const fileName&);
-            void operator=(const word&);
-            void operator=(const string&);
-            void operator=(const std::string&);
-            void operator=(const char*);
+        //- Copy, stripping invalid characters
+        void operator=(const char* str);
 
 
     // IOstream operators
 
-        friend Istream& operator>>(Istream&, fileName&);
-        friend Ostream& operator<<(Ostream&, const fileName&);
+        friend Istream& operator>>(Istream& is, fileName& fn);
+        friend Ostream& operator<<(Ostream& os, const fileName& fn);
 };
 
 
-//- Assemble words and fileNames as pathnames by adding a '/' separator
-fileName operator/(const string&, const string&);
+//- Assemble words and fileNames as pathnames by adding a '/' separator.
+//  No '/' separator is added if either argument is an empty string.
+fileName operator/(const string& a, const string& b);
 
 
 //- Recursively search the given directory for the file
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileNameI.H b/src/OpenFOAM/primitives/strings/fileName/fileNameI.H
index 6c2bb37f9ac16e402f3fee80a27b8c41087fb37a..e2055f74746685b214aa0b9fe605543eebc7015f 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileNameI.H
+++ b/src/OpenFOAM/primitives/strings/fileName/fileNameI.H
@@ -25,6 +25,21 @@ License
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
+inline std::string::size_type Foam::fileName::find_ext() const
+{
+    const size_type i = find_last_of("./");
+
+    if (i == npos || i == 0 || operator[](i) == '/')
+    {
+        return npos;
+    }
+    else
+    {
+        return i;
+    }
+}
+
+
 inline void Foam::fileName::stripInvalid()
 {
     // skip stripping unless debug is active to avoid
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C b/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C
index b87431ca5c96f6cf5bf761d89fdeecf1c67d84e6..dd686bb1314107e4630f6bf1e9f01a022f57580e 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C
+++ b/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C
@@ -78,5 +78,3 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const fileName& fn)
 
 
 // ************************************************************************* //
-
-