41

What is the best way to match fully qualified Java class name in a text?

Examples: java.lang.Reflect, java.util.ArrayList, org.hibernate.Hibernate.

Lii
  • 9,906
  • 6
  • 53
  • 73
Chun ping Wang
  • 3,619
  • 12
  • 39
  • 53

9 Answers9

77

A Java fully qualified class name (lets say "N") has the structure

N.N.N.N

The "N" part must be a Java identifier. Java identifiers cannot start with a number, but after the initial character they may use any combination of letters and digits, underscores or dollar signs:

([a-zA-Z_$][a-zA-Z\d_$]*\.)*[a-zA-Z_$][a-zA-Z\d_$]*
------------------------    -----------------------
          N                           N

They can also not be a reserved word (like import, true or null). If you want to check plausibility only, the above is enough. If you also want to check validity, you must check against a list of reserved words as well.

Java identifiers may contain any Unicode letter instead of "latin only". If you want to check for this as well, use Unicode character classes:

([\p{Letter}_$][\p{Letter}\p{Number}_$]*\.)*[\p{Letter}_$][\p{Letter}\p{Number}_$]*

or, for short

([\p{L}_$][\p{L}\p{N}_$]*\.)*[\p{L}_$][\p{L}\p{N}_$]*

The Java Language Specification, (section 3.8) has all details about valid identifier names.

Also see the answer to this question: Java Unicode variable names

Community
  • 1
  • 1
Tomalak
  • 306,836
  • 62
  • 485
  • 598
  • 2
    Java identifiers can start with any currency symbol so $val, £val and ¥val are all valid. I think this is applies to classes as well as variables. See the java api http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Character.html#isJavaIdentifierStart(char) – Richard Miskin Mar 05 '11 at 19:02
  • @Richard: Okay, thanks for the info. Then `\p{Currency_Symbol}` or `\p{Sc}` should be used instead of `$`. Thinking about it, a small parser that calls `isJavaIdentifierPart()` and `isJavaIdentifierStart()` repeatedly would result in cleaner code. – Tomalak Mar 05 '11 at 19:15
  • I agree a parser is the way to do it, it's almost as if the Java language designers wrote the Character API with this in mind ;) However the question is about a regex so I think you've got the correct answer. +1 from me. – Richard Miskin Mar 05 '11 at 19:20
  • 32
    Actually, those methods are already represented by special character classes. All we need to match a Java identifier is `"(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"`. Elegance, thy name is Java! – Alan Moore Mar 05 '11 at 22:01
  • @Alan: Very nice! Thank you. :-) – Tomalak Mar 06 '11 at 07:51
  • @AlanMoore: Very nice. Unfortunately, RegexBuddy 3.6.2 doesn't recognize these classes under its Java flavor. It also doesn't recognize `\p{Currency_Symbol}`, but it does recognize `\p{Sc}`. Haven't tested much further, but I'm going to have to, because RegexBuddy is pretty important to my workflow. – aliteralmind Mar 28 '14 at 01:43
  • 1
    RegexBuddy can handle this: `([\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*\.)+` – aliteralmind Mar 28 '14 at 01:49
  • Yeah, RegexBuddy 4 doesn't recognize the "javaJava" categories either, which is just as well. But RB4 has made huge improvements, both in the number of flavors covered and in the thoroughness of the coverage. Nice new features, too; the flavor conversion feature alone is worth the price of the upgrade. – Alan Moore Mar 28 '14 at 03:33
  • I think the first part shouldn't include the "$" character. – android developer May 11 '15 at 09:56
  • @androiddeveloper `$` is a valid identifier start character in Java. – Tomalak May 11 '15 at 09:58
  • @Tomalak It does? but isn't it for telling of an inner class? What does it mean if you put it in the beginning ? – android developer May 11 '15 at 10:00
  • I suppose it would take you less than five minutes to try it and find out. – Tomalak May 11 '15 at 10:01
  • @Tomalak Well on Android, it's limited to just English letters, digits, and underscore : http://developer.android.com/guide/topics/manifest/manifest-element.html#package . It was a long time ago that I've written pure, official Java. I've now searched for "$ character package name" on Google, and I don't think it show the answer ... – android developer May 11 '15 at 10:06
  • That page outlines conventions that apply to Android development, not rules that apply to the Java language. That's here: https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8. It states that the `$` *should* not be used, it does not state that it is illegal. – Tomalak May 11 '15 at 11:00
  • @androiddeveloper also "Java-language-**style** package name" is not the same thing as "JLS conformant package name". – TWiStErRob Aug 19 '16 at 16:43
8

Here is a fully working class with tests, based on the excellent comment from @alan-moore

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.regex.Pattern;

import org.junit.Test;

public class ValidateJavaIdentifier {

    private static final String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
    private static final Pattern FQCN = Pattern.compile(ID_PATTERN + "(\\." + ID_PATTERN + ")*");

    public static boolean validateJavaIdentifier(String identifier) {
        return FQCN.matcher(identifier).matches();
    }


    @Test
    public void testJavaIdentifier() throws Exception {
        assertTrue(validateJavaIdentifier("C"));
        assertTrue(validateJavaIdentifier("Cc"));
        assertTrue(validateJavaIdentifier("b.C"));
        assertTrue(validateJavaIdentifier("b.Cc"));
        assertTrue(validateJavaIdentifier("aAa.b.Cc"));
        assertTrue(validateJavaIdentifier("a.b.Cc"));

        // after the initial character identifiers may use any combination of
        // letters and digits, underscores or dollar signs
        assertTrue(validateJavaIdentifier("a.b.C_c"));
        assertTrue(validateJavaIdentifier("a.b.C$c"));
        assertTrue(validateJavaIdentifier("a.b.C9"));

        assertFalse("cannot start with a dot", validateJavaIdentifier(".C"));
        assertFalse("cannot have two dots following each other",
                validateJavaIdentifier("b..C"));
        assertFalse("cannot start with a number ",
                validateJavaIdentifier("b.9C"));
    }
}
Community
  • 1
  • 1
Renaud
  • 14,430
  • 4
  • 73
  • 75
  • `VALID_JAVA_IDENTIFIER` is bad choice for the name as that pattern represents a FQCN. I suggest extracting `String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"` to make it more obvious and readable. – TWiStErRob Aug 19 '16 at 16:51
  • @TWiStErRob not sure what you mean when you say that `VALID_JAVA_IDENTIFIER` represents a FQCN? Plus, not sure `ID_PATTERN` is more readable... Thanks for explaining. – Renaud Aug 22 '16 at 04:54
  • A valid java identifier can be method name, local variable, class name, subpackage name, etc.. Your "VALID_JAVA_IDENTIFIER" pattern, however, matches a fully qualified class name (FQCN) consisting of multiple identifiers (one for each subpackage + class name). FQCN is **not** a valid java identifier, because it contains dots. For `ID_PATTERN` see my edit on Jörgen's answer; it's easier to see what gets repeated and when, you also don't have to scroll or break lines. – TWiStErRob Aug 22 '16 at 09:05
7

The pattern provided by Renaud works, but his original answer will always backtrack at the end.

To optimize it, you can essentially swap the first half with the last. Note the dot match that you also need to change.

The following is my version of it that, when compared to the original, runs about twice as fast:

String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
Pattern FQCN = Pattern.compile(ID_PATTERN + "(\\." + ID_PATTERN + ")*");

I cannot write comments, so I decided to write an answer instead.

zb226
  • 7,475
  • 4
  • 37
  • 64
3

I came (on my own) to a similar answer (as Tomalak's answer), something as M.M.M.N:

([a-z][a-z_0-9]*\.)*[A-Z_]($[A-Z_]|[\w_])*

Where,

M = ([a-z][a-z_0-9]*\.)*
N = [A-Z_]($[A-Z_]|[\w_])*

However, this regular expression (unlike Tomalak's answer) makes more assumptions:

  1. The package name (The M part) will be only in lower case, the first character of M will be always a lower letter, the rest can mix underscore, lower letters and numbers.

  2. The Class Name (the N part) will always start with an Upper Case Letter or an underscore, the rest can mix underscore, letters and numbers. Inner Classes will always start with a dollar symbol ($) and must obey the class name rules described previously.

Note: the pattern \w is the XSD pattern for letters and digits (it does not includes the underscore symbol (_))

Hope this help.

Carlitos Way
  • 2,659
  • 16
  • 28
0

Following expression works perfectly fine for me.

^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$
gopalanrc
  • 198
  • 1
  • 11
0

The following class validates that a provided package name is valid:

import java.util.HashSet;

public class ValidationUtils {

    // All Java reserved words that must not be used in a valid package name.
    private static final HashSet reserved;

    static {
        reserved = new HashSet();
        reserved.add("abstract");reserved.add("assert");reserved.add("boolean");
        reserved.add("break");reserved.add("byte");reserved.add("case");
        reserved.add("catch");reserved.add("char");reserved.add("class");
        reserved.add("const");reserved.add("continue");reserved.add("default");
        reserved.add("do");reserved.add("double");reserved.add("else");
        reserved.add("enum");reserved.add("extends");reserved.add("false");
        reserved.add("final");reserved.add("finally");reserved.add("float");
        reserved.add("for");reserved.add("if");reserved.add("goto");
        reserved.add("implements");reserved.add("import");reserved.add("instanceof");
        reserved.add("int");reserved.add("interface");reserved.add("long");
        reserved.add("native");reserved.add("new");reserved.add("null");
        reserved.add("package");reserved.add("private");reserved.add("protected");
        reserved.add("public");reserved.add("return");reserved.add("short");
        reserved.add("static");reserved.add("strictfp");reserved.add("super");
        reserved.add("switch");reserved.add("synchronized");reserved.add("this");
        reserved.add("throw");reserved.add("throws");reserved.add("transient");
        reserved.add("true");reserved.add("try");reserved.add("void");
        reserved.add("volatile");reserved.add("while");
    }

    /**
     * Checks if the string that is provided is a valid Java package name (contains only
     * [a-z,A-Z,_,$], every element is separated by a single '.' , an element can't be one of Java's
     * reserved words.
     *
     * @param name The package name that needs to be validated.
     * @return <b>true</b> if the package name is valid, <b>false</b> if its not valid.
     */
    public static final boolean isValidPackageName(String name) {
        String[] parts=name.split("\\.",-1);
        for (String part:parts){
            System.out.println(part);
            if (reserved.contains(part)) return false;
            if (!validPart(part)) return false;
        }
        return true;
    }

    /**
     * Checks that a part (a word between dots) is a valid part to be used in a Java package name.
     * @param part The part between dots (e.g. *PART*.*PART*.*PART*.*PART*).
     * @return <b>true</b> if the part is valid, <b>false</b> if its not valid.
     */
    private static boolean validPart(String part){
        if (part==null || part.length()<1){
            // Package part is null or empty !
            return false;
        }
        if (Character.isJavaIdentifierStart(part.charAt(0))){
            for (int i = 0; i < part.length(); i++){
                char c = part.charAt(i);
                if (!Character.isJavaIdentifierPart(c)){
                    // Package part contains invalid JavaIdentifier !
                    return false;
                }
            }
        }else{
            // Package part does not begin with a valid JavaIdentifier !
            return false;
        }

        return true;
    }
}
David Lev
  • 733
  • 4
  • 12
0

shorter version of a working regexp:

\p{Alnum}[\p{Alnum}._]+\p{Alnum}
Diego Plentz
  • 5,527
  • 2
  • 26
  • 31
0

For string like com.mycompany.core.functions.CustomFunction I'm using ((?:(?:\w+)?\.[a-z_A-Z]\w+)+)

Georgi Stoyanov
  • 433
  • 1
  • 5
  • 23
-3

I'll say something like ([\w]+\.)*[\w]+

But maybe I can be more specific knowing what you want to do with it ;)

krtek
  • 25,218
  • 5
  • 53
  • 79
  • You don't need the `[]`, this should be enough `(\\w+\\.?)+` – Johan Sjöberg Mar 05 '11 at 17:30
  • I think the [] makes things clearer, regexp are already messy enough ;) and I let the last bit outside to clearly separate packages from class name. – krtek Mar 05 '11 at 17:32
  • I want to see if the given input is a good java class name (fully qualify package), using hibernate validatior (annotation style via @Pattern). – Chun ping Wang Mar 07 '11 at 06:43