From b54ca0f86ad5eaf2dbf57d13ab18bb45fe690401 Mon Sep 17 00:00:00 2001 From: tball Date: Thu, 20 Aug 2015 11:03:02 -0700 Subject: [PATCH] Added support for wildcards in package prefix flags. Change on 2015/08/20 by tball ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=101138655 --- .../com/google/devtools/j2objc/Options.java | 17 +- .../devtools/j2objc/util/NameTable.java | 106 +-------- .../devtools/j2objc/util/PackagePrefixes.java | 217 ++++++++++++++++++ .../google/devtools/j2objc/OptionsTest.java | 20 -- .../google/devtools/j2objc/SmallTests.java | 2 + .../devtools/j2objc/util/NameTableTest.java | 20 -- .../j2objc/util/PackagePrefixesTest.java | 111 +++++++++ 7 files changed, 341 insertions(+), 152 deletions(-) create mode 100644 translator/src/main/java/com/google/devtools/j2objc/util/PackagePrefixes.java create mode 100644 translator/src/test/java/com/google/devtools/j2objc/util/PackagePrefixesTest.java diff --git a/translator/src/main/java/com/google/devtools/j2objc/Options.java b/translator/src/main/java/com/google/devtools/j2objc/Options.java index 976be93d32..6ab10b6fc9 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/Options.java +++ b/translator/src/main/java/com/google/devtools/j2objc/Options.java @@ -26,6 +26,7 @@ import com.google.devtools.j2objc.util.ErrorUtil; import com.google.devtools.j2objc.util.FileUtil; import com.google.devtools.j2objc.util.HeaderMap; +import com.google.devtools.j2objc.util.PackagePrefixes; import java.io.File; import java.io.FileInputStream; @@ -83,7 +84,8 @@ public class Options { private boolean staticAccessorMethods = false; private int batchTranslateMaximum = 0; private List headerMappingFiles = null; - private Map packagePrefixes = Maps.newHashMap(); + + private PackagePrefixes packagePrefixes = new PackagePrefixes(); private static final Set VALID_JAVA_VERSIONS = ImmutableSet.of("1.8", "1.7", "1.6", "1.5"); @@ -441,14 +443,7 @@ private static void addPrefixesFile(String filename) throws IOException { FileInputStream fis = new FileInputStream(filename); props.load(fis); fis.close(); - addPrefixProperties(props); - } - - @VisibleForTesting - static void addPrefixProperties(Properties props) { - for (String pkg : props.stringPropertyNames()) { - addPackagePrefix(pkg, props.getProperty(pkg).trim()); - } + instance.packagePrefixes.addPrefixProperties(props); } private void addMappingsFiles(String[] filenames) throws IOException { @@ -682,12 +677,12 @@ public static List getBootClasspath() { return getPathArgument(bootclasspath); } - public static Map getPackagePrefixes() { + public static PackagePrefixes getPackagePrefixes() { return instance.packagePrefixes; } public static void addPackagePrefix(String pkg, String prefix) { - addMapping(instance.packagePrefixes, pkg, prefix, "package prefix"); + instance.packagePrefixes.addPrefix(pkg, prefix); } public static String fileEncoding() { diff --git a/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java b/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java index 70b8109276..e617d6c224 100644 --- a/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java +++ b/translator/src/main/java/com/google/devtools/j2objc/util/NameTable.java @@ -16,7 +16,6 @@ package com.google.devtools.j2objc.util; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; @@ -26,7 +25,6 @@ import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.PackageDeclaration; -import com.google.devtools.j2objc.file.InputFile; import com.google.devtools.j2objc.types.GeneratedVariableBinding; import com.google.devtools.j2objc.types.IOSMethodBinding; import com.google.devtools.j2objc.types.PointerTypeBinding; @@ -41,7 +39,6 @@ import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import java.io.File; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -274,7 +271,7 @@ public class NameTable { * can share a prefix; for example, the com.google.common packages in * Guava could share a "GG" (Google Guava) or simply "Guava" prefix. */ - private final Map prefixMap; + private final PackagePrefixes prefixMap; private final Map methodMappings; @@ -286,7 +283,7 @@ public static class Factory { // Currently a shared map. // TODO(kstanger): For thread safety this will either need to be a // concurrent map, or make a copy for each NameTable. - private Map prefixMap = Options.getPackagePrefixes(); + private PackagePrefixes prefixMap = Options.getPackagePrefixes(); private final Map methodMappings = ImmutableMap.copyOf( Maps.transformValues(Options.getMethodMappings(), EXTRACT_SELECTOR_FUNC)); @@ -308,7 +305,7 @@ public String apply(String value) { }; private NameTable( - Types typeEnv, Map prefixMap, Map methodMappings) { + Types typeEnv, PackagePrefixes prefixMap, Map methodMappings) { this.typeEnv = typeEnv; this.prefixMap = prefixMap; this.methodMappings = methodMappings; @@ -943,104 +940,11 @@ public static String getMainTypeFullName(CompilationUnit unit) { } } - @VisibleForTesting - public void mapPackageToPrefix(String packageName, String prefix) { - prefixMap.put(packageName, prefix); - } - - /** - * Return the prefix for a specified package. If a prefix was specified - * for the package, then that prefix is returned. Otherwise, a camel-cased - * prefix is created from the package name. - */ public String getPrefix(IPackageBinding packageBinding) { - String packageName = packageBinding.getName(); - if (hasPrefix(packageName)) { - return prefixMap.get(packageName); - } - - for (IAnnotationBinding annotation : packageBinding.getAnnotations()) { - if (annotation.getName().endsWith("ObjectiveCName")) { - String prefix = (String) BindingUtil.getAnnotationValue(annotation, "value"); - prefixMap.put(packageName, prefix); - // Don't return, as there may be a prefix annotation that overrides this value. - } - } - - String prefix = getPrefixFromPackageInfoSource(packageBinding); - if (prefix == null) { - prefix = getPrefixFromPackageInfoClass(packageName); - } - if (prefix == null) { - prefix = camelCaseQualifiedName(packageName); - } - prefixMap.put(packageName, prefix); - return prefix; - } - - /** - * Check if there is a package-info.java source file with a prefix annotation. - */ - private static String getPrefixFromPackageInfoSource(IPackageBinding packageBinding) { - try { - String qualifiedName = "package-info"; - String packageName = packageBinding.getName(); - // Path will be null if this is the empty package. - if (packageName != null) { - qualifiedName = packageName + '.' + qualifiedName; - } - InputFile file = FileUtil.findOnSourcePath(qualifiedName); - if (file != null) { - String pkgInfo = FileUtil.readFile(file); - int i = pkgInfo.indexOf("@ObjectiveCName"); - if (i == -1) { - i = pkgInfo.indexOf("@com.google.j2objc.annotations.ObjectiveCName"); - } - if (i > -1) { - // Extract annotation's value string. - i = pkgInfo.indexOf('"', i + 1); - if (i > -1) { - int j = pkgInfo.indexOf('"', i + 1); - if (j > -1) { - return pkgInfo.substring(i + 1, j); - } - } - } - } - } catch (IOException e) { - // Continue, as there's no package-info to check. - } - return null; - } - - /** - * Check if there is a package-info class with a prefix annotation. - */ - private static String getPrefixFromPackageInfoClass(String packageName) { - List paths = Options.getBootClasspath(); - paths.addAll(Options.getClassPathEntries()); - PathClassLoader classLoader = new PathClassLoader(paths); - try { - Class clazz = classLoader.loadClass(packageName + ".package-info"); - ObjectiveCName objectiveCName = clazz.getAnnotation(ObjectiveCName.class); - if (objectiveCName != null) { - return objectiveCName.value(); - } - } catch (ClassNotFoundException e) { - // Class does not exist -- ignore exception. - } catch (SecurityException e) { - // Failed fetching a package-info class from a secure package -- ignore exception. - } finally { - try { - classLoader.close(); - } catch (IOException e) { - // Ignore, any open files will be closed on exit. - } - } - return null; + return prefixMap.getPrefix(packageBinding); } public boolean hasPrefix(String packageName) { - return prefixMap.containsKey(packageName); + return prefixMap.hasPrefix(packageName); } } diff --git a/translator/src/main/java/com/google/devtools/j2objc/util/PackagePrefixes.java b/translator/src/main/java/com/google/devtools/j2objc/util/PackagePrefixes.java new file mode 100644 index 0000000000..05be5f5606 --- /dev/null +++ b/translator/src/main/java/com/google/devtools/j2objc/util/PackagePrefixes.java @@ -0,0 +1,217 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.devtools.j2objc.util; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.devtools.j2objc.Options; +import com.google.devtools.j2objc.file.InputFile; +import com.google.j2objc.annotations.ObjectiveCName; + +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IPackageBinding; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Pattern; + +/** + * Class that creates and stores the prefixes associated with Java packages. + * Prefixes can be defined in several ways: + *
    + *
  • Using a --prefix command-line flag,
  • + *
  • In a properties file specified by a --prefixes command-line flag,
  • + *
  • By an ObjectiveCName annotation in a package-info.java source file, or
  • + *
  • By camel-casing the package name (default). + *
+ * + * Command-line wildcard flags (either separately or in a properties file) are + * also supported, which map multiple packages to a single prefix. For example, + * 'com.google.devtools.j2objc.*=J2C' specifies that all translator classes have + * a J2C prefix, but not the com.google.j2objc.annotations classes. Wildcard + * declarations are matched in the order they are declared. + * + * @author Tom Ball + */ +public final class PackagePrefixes { + + private Map mappedPrefixes = Maps.newHashMap(); + + // A key array is used so that wildcards are checked in declared order. + // There is one wildcard value for each key, enforced within this class. + // Too bad there's no available equivalent to android.util.ArrayMap. + private List wildcardKeys = Lists.newArrayList(); + private List wildcardValues = Lists.newArrayList(); + + @VisibleForTesting + String getPrefix(String pkg) { + String value = mappedPrefixes.get(pkg); + if (value != null) { + return value; + } + + for (int i = 0; i < wildcardKeys.size(); i++) { + Pattern p = wildcardKeys.get(i); + if (p.matcher(pkg).matches()) { + value = wildcardValues.get(i); + mappedPrefixes.put(pkg, value); + return value; + } + } + + return null; + } + + public void addPrefix(String pkg, String prefix) { + if (pkg == null || prefix == null) { + throw new IllegalArgumentException("null package or prefix specified"); + } + if (pkg.contains("*")) { + String regex = wildcardToRegex(pkg); + for (int i = 0; i < wildcardKeys.size(); i++) { + if (regex.equals(wildcardKeys.get(i).toString())) { + String oldPrefix = wildcardValues.get(i); + if (!prefix.equals(oldPrefix)) { + ErrorUtil.error("package prefix redefined; was \"" + oldPrefix + ", now " + prefix); + } + return; + } + } + wildcardKeys.add(Pattern.compile(regex)); + wildcardValues.add(prefix); + } else { + mappedPrefixes.put(pkg, prefix); + } + } + + public boolean hasPrefix(String packageName) { + return getPrefix(packageName) != null; + } + + /** + * Return the prefix for a specified package. If a prefix was specified + * for the package, then that prefix is returned. Otherwise, a camel-cased + * prefix is created from the package name. + */ + public String getPrefix(IPackageBinding packageBinding) { + String packageName = packageBinding.getName(); + if (hasPrefix(packageName)) { + return getPrefix(packageName); + } + + for (IAnnotationBinding annotation : packageBinding.getAnnotations()) { + if (annotation.getName().endsWith("ObjectiveCName")) { + String prefix = (String) BindingUtil.getAnnotationValue(annotation, "value"); + addPrefix(packageName, prefix); + // Don't return, as there may be a prefix annotation that overrides this value. + } + } + + String prefix = getPrefixFromPackageInfoSource(packageBinding); + if (prefix == null) { + prefix = getPrefixFromPackageInfoClass(packageName); + } + if (prefix == null) { + prefix = NameTable.camelCaseQualifiedName(packageName); + } + addPrefix(packageName, prefix); + return prefix; + } + + /** + * Check if there is a package-info.java source file with a prefix annotation. + */ + private String getPrefixFromPackageInfoSource(IPackageBinding packageBinding) { + try { + String qualifiedName = "package-info"; + String packageName = packageBinding.getName(); + // Path will be null if this is the empty package. + if (packageName != null) { + qualifiedName = packageName + '.' + qualifiedName; + } + InputFile file = FileUtil.findOnSourcePath(qualifiedName); + if (file != null) { + String pkgInfo = FileUtil.readFile(file); + int i = pkgInfo.indexOf("@ObjectiveCName"); + if (i == -1) { + i = pkgInfo.indexOf("@com.google.j2objc.annotations.ObjectiveCName"); + } + if (i > -1) { + // Extract annotation's value string. + i = pkgInfo.indexOf('"', i + 1); + if (i > -1) { + int j = pkgInfo.indexOf('"', i + 1); + if (j > -1) { + return pkgInfo.substring(i + 1, j); + } + } + } + } + } catch (IOException e) { + // Continue, as there's no package-info to check. + } + return null; + } + + /** + * Check if there is a package-info class with a prefix annotation. + */ + private String getPrefixFromPackageInfoClass(String packageName) { + List paths = Options.getBootClasspath(); + paths.addAll(Options.getClassPathEntries()); + PathClassLoader classLoader = new PathClassLoader(paths); + try { + Class clazz = classLoader.loadClass(packageName + ".package-info"); + ObjectiveCName objectiveCName = clazz.getAnnotation(ObjectiveCName.class); + if (objectiveCName != null) { + return objectiveCName.value(); + } + } catch (ClassNotFoundException e) { + // Class does not exist -- ignore exception. + } catch (SecurityException e) { + // Failed fetching a package-info class from a secure package -- ignore exception. + } finally { + try { + classLoader.close(); + } catch (IOException e) { + // Ignore, any open files will be closed on exit. + } + } + return null; + } + + /** + * Add a set of package=prefix properties. + */ + public void addPrefixProperties(Properties props) { + for (String pkg : props.stringPropertyNames()) { + addPrefix(pkg, props.getProperty(pkg).trim()); + } + } + + @VisibleForTesting + static String wildcardToRegex(String s) { + if (s.endsWith(".*")) { + // Include root package in regex. For example, foo.bar.* needs to match + // foo.bar, foo.bar.mumble, etc. + String root = s.substring(0, s.length() - 2).replace(".", "\\."); + return String.format("^(%s|%s\\..*)$", root, root); + } + return String.format("^%s$", s.replace(".", "\\.").replace("\\*", ".*")); + } +} diff --git a/translator/src/test/java/com/google/devtools/j2objc/OptionsTest.java b/translator/src/test/java/com/google/devtools/j2objc/OptionsTest.java index a44582a4b8..2a8cd017e7 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/OptionsTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/OptionsTest.java @@ -15,9 +15,6 @@ package com.google.devtools.j2objc; import java.io.IOException; -import java.io.StringReader; -import java.util.Map; -import java.util.Properties; /** * Tests for {@link Options}. @@ -26,23 +23,6 @@ */ public class OptionsTest extends GenerationTest { - /** - * Regression test for http://code.google.com/p/j2objc/issues/detail?id=100. - */ - public void testPackagePrefixesWithTrailingSpace() throws IOException { - String prefixes = - "# Prefix mappings\n" - + "java.lang: JL\n" - + "foo.bar: FB \n"; // Trailing space should be ignored. - StringReader reader = new StringReader(prefixes); - Properties properties = new Properties(); - properties.load(reader); - Options.addPrefixProperties(properties); - Map prefixMap = Options.getPackagePrefixes(); - assertEquals("JL", prefixMap.get("java.lang")); - assertEquals("FB", prefixMap.get("foo.bar")); - } - public void testSourceVersionFlags() throws IOException { // TODO(kirbs): Uncomment following lines and lines in Options when we enable automatic version // detection. Currently this is breaking pulse builds using 64 bit Java 8, and upgrading to diff --git a/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java b/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java index cf756c3079..9f142dd7a5 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java +++ b/translator/src/test/java/com/google/devtools/j2objc/SmallTests.java @@ -72,6 +72,7 @@ import com.google.devtools.j2objc.util.ErrorUtilTest; import com.google.devtools.j2objc.util.FileUtilTest; import com.google.devtools.j2objc.util.NameTableTest; +import com.google.devtools.j2objc.util.PackagePrefixesTest; import com.google.devtools.j2objc.util.ProGuardUsageParserTest; import com.google.devtools.j2objc.util.UnicodeUtilsTest; @@ -125,6 +126,7 @@ public class SmallTests { OptionsTest.class, OuterReferenceFixerTest.class, OuterReferenceResolverTest.class, + PackagePrefixesTest.class, PrimitiveArrayTest.class, PrivateDeclarationResolverTest.class, ProGuardUsageParserTest.class, diff --git a/translator/src/test/java/com/google/devtools/j2objc/util/NameTableTest.java b/translator/src/test/java/com/google/devtools/j2objc/util/NameTableTest.java index b700f7fe52..6e53abe077 100644 --- a/translator/src/test/java/com/google/devtools/j2objc/util/NameTableTest.java +++ b/translator/src/test/java/com/google/devtools/j2objc/util/NameTableTest.java @@ -35,26 +35,6 @@ */ public class NameTableTest extends GenerationTest { - // Verify class name with prefix. - public void testGetFullNameWithPrefix() { - String source = "package foo.bar; public class SomeClass {}"; - CompilationUnit unit = translateType("SomeClass", source); - NameTable nameTable = unit.getNameTable(); - AbstractTypeDeclaration decl = unit.getTypes().get(0); - nameTable.mapPackageToPrefix("foo.bar", "FB"); - assertEquals("FBSomeClass", nameTable.getFullName(decl.getTypeBinding())); - } - - // Verify inner class name with prefix. - public void testGetFullNameWithInnerClassAndPrefix() { - String source = "package foo.bar; public class SomeClass { static class Inner {}}"; - CompilationUnit unit = translateType("SomeClass", source); - NameTable nameTable = unit.getNameTable(); - AbstractTypeDeclaration decl = unit.getTypes().get(1); - nameTable.mapPackageToPrefix("foo.bar", "FB"); - assertEquals("FBSomeClass_Inner", nameTable.getFullName(decl.getTypeBinding())); - } - // Verify class name without package is unchanged. public void testGetFullNameNoPackage() { String source = "public class SomeClass {}"; diff --git a/translator/src/test/java/com/google/devtools/j2objc/util/PackagePrefixesTest.java b/translator/src/test/java/com/google/devtools/j2objc/util/PackagePrefixesTest.java new file mode 100644 index 0000000000..c6d34767fc --- /dev/null +++ b/translator/src/test/java/com/google/devtools/j2objc/util/PackagePrefixesTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.devtools.j2objc.util; + +import com.google.devtools.j2objc.GenerationTest; +import com.google.devtools.j2objc.Options; +import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; +import com.google.devtools.j2objc.ast.CompilationUnit; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Properties; + +/** + * Unit tests for {@link PackagePrefixes}. + * + * @author Tom Ball + */ +public class PackagePrefixesTest extends GenerationTest { + + public void testPackagePrefixesFile() throws IOException { + String prefixes = + "# Prefix mappings\n" + + "java.lang: JL\n" + + "foo.bar: FB\n"; + StringReader reader = new StringReader(prefixes); + Properties properties = new Properties(); + properties.load(reader); + PackagePrefixes prefixMap = Options.getPackagePrefixes(); + assertFalse(prefixMap.hasPrefix("java.lang")); + assertFalse(prefixMap.hasPrefix("foo.bar")); + prefixMap.addPrefixProperties(properties); + assertEquals("JL", prefixMap.getPrefix("java.lang")); + assertEquals("FB", prefixMap.getPrefix("foo.bar")); + } + + /** + * Regression test for http://code.google.com/p/j2objc/issues/detail?id=100. + */ + public void testPackagePrefixesWithTrailingSpace() throws IOException { + String prefixes = + "# Prefix mappings\n" + + "java.lang: JL\n" + + "foo.bar: FB \n"; // Trailing space should be ignored. + StringReader reader = new StringReader(prefixes); + Properties properties = new Properties(); + properties.load(reader); + PackagePrefixes prefixMap = Options.getPackagePrefixes(); + prefixMap.addPrefixProperties(properties); + assertEquals("JL", prefixMap.getPrefix("java.lang")); + assertEquals("FB", prefixMap.getPrefix("foo.bar")); + } + + // Verify class name with prefix. + public void testGetFullNameWithPrefix() { + String source = "package foo.bar; public class SomeClass {}"; + Options.addPackagePrefix("foo.bar", "FB"); + CompilationUnit unit = translateType("SomeClass", source); + NameTable nameTable = unit.getNameTable(); + AbstractTypeDeclaration decl = unit.getTypes().get(0); + assertEquals("FBSomeClass", nameTable.getFullName(decl.getTypeBinding())); + } + + // Verify inner class name with prefix. + public void testGetFullNameWithInnerClassAndPrefix() { + String source = "package foo.bar; public class SomeClass { static class Inner {}}"; + Options.addPackagePrefix("foo.bar", "FB"); + CompilationUnit unit = translateType("SomeClass", source); + NameTable nameTable = unit.getNameTable(); + AbstractTypeDeclaration decl = unit.getTypes().get(1); + assertEquals("FBSomeClass_Inner", nameTable.getFullName(decl.getTypeBinding())); + } + + public void testPackageWildcards() throws IOException { + String source = "package foo.bar; public class SomeClass {}"; + Options.addPackagePrefix("foo.*", "FB"); + CompilationUnit unit = translateType("SomeClass", source); + NameTable nameTable = unit.getNameTable(); + AbstractTypeDeclaration decl = unit.getTypes().get(0); + assertEquals("FBSomeClass", nameTable.getFullName(decl.getTypeBinding())); + } + + public void testWildcardToRegex() throws IOException { + // Verify normal package name only matches itself. + String regex = PackagePrefixes.wildcardToRegex("com.google.j2objc"); + assertEquals("^com\\.google\\.j2objc$", regex); + assertTrue("com.google.j2objc".matches(regex)); + assertFalse("com google j2objc".matches(regex)); // Would match if wildcard wasn't converted. + assertFalse("com.google.j2objc.annotations".matches(regex)); + + regex = PackagePrefixes.wildcardToRegex("foo.bar.*"); + assertEquals("^(foo\\.bar|foo\\.bar\\..*)$", regex); + assertTrue("foo.bar".matches(regex)); + assertTrue("foo.bar.mumble".matches(regex)); + assertFalse("foo.bars".matches(regex)); + } +}