4
\$\begingroup\$

I'm currently working on a solution to add a number to a file name if the number does not exist or increase de number by one if the number exist.

For instance if you input "filename.jpeg" the code will add a 1 and return "filename(1).jpeg".

In case the number already exist in the name for instance "hello_world(3).pdf" it will increase it by one and return "hello_world(4).pdf".

This logic is used to add files to a Recycle Bin where files will be sent from different parts of the system and they need to have unique names.

The code must

  • Find a number between parenthesis next to the file extension
  • If this number does NOT exist add it
  • If it does exist increase it by one

(another method will run this iteratively until the name is is not found in the Recycle Bin)

Any suggestions or improvements ?

Should you require any information please do not hesitate to ask away !

public String getFileNameNumbered(String fileName){

    int indexLastDot = fileName.lastIndexOf('.');
    String extension = fileName.substring(indexLastDot);
    String regex = "[(]\\d+[)]" + extension + "$";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(fileName);

    if(matcher.find()){ //Check if the file name contains a number eg. (1).jpeg
        String numberExtension = matcher.group();
        pattern = Pattern.compile("\\d+");
        matcher = pattern.matcher(numberExtension);
        if(matcher.find()){ // check only for the number and extract it
            int number = Integer.parseInt(matcher.group());
            number++;
            fileName = fileName.replaceAll(regex, "(" + number + ")" + extension);
        }

    } else { //add a number to the file
        fileName = fileName.substring(0, indexLastDot) + "(" + 1 + ")" + extension;
    }
    return fileName;
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ What if the filename after you add the number still clashes with an existing file? \$\endgroup\$ Jul 11, 2022 at 21:20

2 Answers 2

3
\$\begingroup\$

Bugs

What happens when you encounter a file like README with no extension?

Although unlikely, your extension could contain characters that have special meaning in a regex pattern, so must be quoted:

    String regex = "[(]\\d+[)]" + Pattern.quote(extension) + "$";

Regex

You are not using all of the power of the regex engine. Moreover, you are creating possibly dozens of regex Pattern objects where a single regex would do. Additionally, you are using \d+ to find the digits in a string where the first character is known to be a ( and the string ends with ) followed by extension (a string of known length) so could easily be extracted using just a substring operation.

You want to find something like:

a.filename(99).ext

So let's design a regex which will do everything. It will extract the base name, an optional number in parenthesis, and the extension:

(.*?)(?:\((\d+)\))?(\.[^.]*)$
(   )                         - a capturing group of
 .*?                          -   any characters (non-greedy), followed by
     (?:         )            - a non-capturing group of
        \(     \)             -   literal parentheses
          (   )               -   surrounding a capturing group of
           \d+                -     digits
                  ?           -   (zero or one occurrences)
                   (       )  - a capturing group of
                    \.        -   a literal period
                      [^.]*   -   followed by any number of non-periods
                            $ - at the end of the string

The non-greedy initial match is required to prevent (99) from becoming part of the initial filename and the following optional section matching with zero occurrences.

(Of course, you'll have to double all \ characters to create a Java string literal.)

If this regular expression matches:

  • .group(1) will contain the start of the filename,
  • .group(2) will contain 1 or more digits, or will be null,
  • .group(3) will contain the extension.

If the regex does not match, then there is no period in the filename, like README, and you'll have to decide how you want to handle that.

\$\endgroup\$
0
2
\$\begingroup\$

The code must

  • Find a number between parenthesis next to the file extension
  • If this number does NOT exist add it
  • If it does exist increase it by one

So you're operating on a filename string, assuming that incrementing-or-defaulting will be enough for use on the filesystem. This assumption may not hold water.

I suggest that you:

  • do find an index in the provided string, but only to remove it and extract the stem before it and extension after
  • do a directory search for files with the same stem and extension, finding the maximum existing index
  • increment that index
  • form a new filename based on what you know to be the pattern-matched stem, incremented index and pattern-matched extension

Suggested

Very roughly, and without much mind for optimisation:

import java.io.IOException;
import java.nio.file.*;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class Main {
    private static final String barePatternStr =
        "^"          // Start
        + "(?<stem>" // Capture: the filename stem
        +     "[^.]*"  // Any non-dot stem chars
        + ")"        // End of capture
        + "(?:"      // Non-capturing group for index
        +     "\\("  // Literal paren
        +     "\\d+" // At least one digit, greedy
        +     "\\)"  // Literal paren
        + ")?"       // Index is optional
        + "(?<ext>"  // Capture: any extension
        +     "\\."  // Literal dot
        +     ".*?"  // Anything else, lazy
        + ")?"       // Extension is optional
        + "$";       // End

    private static final Pattern barePattern = Pattern.compile(barePatternStr);

    private static final String filesysPatternStr =
        "^"           // Start
        + "%s"        // Stem field (needs to be escaped)
        + "\\("       // Literal paren
        + "(?<index>" // Capture: the numeric index
        +     "\\d+"  // At least one digit, greedy
        + ")"         // End of capture
        + "\\)"       // Literal paren
        + "%s"        // Extension field (needs to be escaped)
        + "$";        // End

    public static void main(String[] args) throws IOException {
        System.out.println(findNext(
            Path.of("."),
            "test.txt"
        ));
    }

    private static String findNext(Path parent, String fileName) throws IOException {
        Matcher bareMatcher = barePattern.matcher(fileName);

        // Ignore any index to form the bare filename
        bareMatcher.matches();
        String stem = bareMatcher.group("stem"),
               ext = bareMatcher.group("ext");
        if (ext == null) ext = "";

        String globStr = filesysPatternStr.formatted(
            Pattern.quote(stem), Pattern.quote(ext)
        );
        int index = getHighestIndex(parent, Pattern.compile(globStr));

        StringBuilder result = new StringBuilder();
        bareMatcher.appendReplacement(
            result,
            "${stem}(%d)${ext}".formatted(index + 1)
        );
        return result.toString();
    }

    private static int getHighestIndex(Path parent, Pattern glob) throws IOException {
        try (Stream<Path> files = Files.list(parent)) {
            return files
                .map(path -> {
                    Matcher globMatcher = glob.matcher(path.getFileName().toString());
                    if (globMatcher.matches())
                        return Integer.parseInt(globMatcher.group("index"));
                    return null;
                })
                .filter(Objects::nonNull)
                .mapToInt(i -> i)
                .max()
                .orElse(0);
        }
    }
}

Performance may vary by doing a first-pass filter on the file system with a simple glob * where the index would be, if the OS and JVM have efficient globbing; but this is illustrative of the algorithm.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ @AJNeufeld Right; that's a consequence of an earlier iteration that had an extension that was too greedy. Should be better now. \$\endgroup\$
    – Reinderien
    Jul 12, 2022 at 0:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.