aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2017-08-29 23:04:18 +0200
committerGravatar Vladimir Moskva <vladmos@google.com>2017-08-30 13:48:22 +0200
commit3e5ec96983f98ea4062ce0ca42df43e484777d1c (patch)
tree9560fe44a6aa19bf2571be6a73ce21eadbe11810 /src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
parent7cc11272581a53e115f672404fb2d8e1dd5d18df (diff)
Add support for the <type>/<package>:<name> method of declaring resources.
RELNOTES: None PiperOrigin-RevId: 166899690
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java711
1 files changed, 364 insertions, 347 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
index 9d0575e2e8..9344412390 100644
--- a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
+++ b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
@@ -45,30 +45,27 @@ import javax.annotation.concurrent.Immutable;
/**
* Represents a fully qualified name for an android resource.
*
- * Each resource name consists of the resource package, name, type, and qualifiers.
+ * <p>Each resource name consists of the resource package, name, type, and qualifiers.
*/
@Immutable
public class FullyQualifiedName implements DataKey {
- /** Represents the type of a {@link FullyQualifiedName}. */
- public interface Type {
- /**
- * The category of type that a {@link Type} can be.
- *
- * <p>
- * <em>Note:</em> used for strict ordering of {@link FullyQualifiedName}s.
- */
- public enum ConcreteType {
- RESOURCE_TYPE,
- VIRTUAL_TYPE;
- }
+ public static final String DEFAULT_PACKAGE = "res-auto";
+ private static final Joiner DASH_JOINER = Joiner.on('-');
+ // To save on memory, always return one instance for each FullyQualifiedName.
+ // Using a HashMap to deduplicate the instances -- the key retrieves a single instance.
+ private static final ConcurrentMap<FullyQualifiedName, FullyQualifiedName> instanceCache =
+ new ConcurrentHashMap<>();
+ private static final AtomicInteger cacheHit = new AtomicInteger(0);
+ private final String pkg;
+ private final ImmutableList<String> qualifiers;
+ private final Type type;
+ private final String name;
- public String getName();
- public ConcreteType getType();
- public boolean isOverwritable(FullyQualifiedName fqn);
- public int compareTo(Type other);
- @Override public boolean equals(Object obj);
- @Override public int hashCode();
- @Override public String toString();
+ private FullyQualifiedName(String pkg, ImmutableList<String> qualifiers, Type type, String name) {
+ this.pkg = pkg;
+ this.qualifiers = qualifiers;
+ this.type = type;
+ this.name = name;
}
private static Type createTypeFrom(String rawType) {
@@ -82,62 +79,216 @@ public class FullyQualifiedName implements DataKey {
return null;
}
- private static class ResourceTypeWrapper implements Type {
- private final ResourceType resourceType;
-
- public ResourceTypeWrapper(ResourceType resourceType) {
- this.resourceType = resourceType;
+ /**
+ * Creates a new FullyQualifiedName with normalized qualifiers.
+ *
+ * @param pkg The resource package of the name. If unknown the default should be "res-auto"
+ * @param qualifiers The resource qualifiers of the name, such as "en" or "xhdpi".
+ * @param type The type of the name.
+ * @param name The name of the name.
+ * @return A new FullyQualifiedName.
+ */
+ public static FullyQualifiedName of(String pkg, List<String> qualifiers, Type type, String name) {
+ checkNotNull(pkg);
+ checkNotNull(qualifiers);
+ checkNotNull(type);
+ checkNotNull(name);
+ ImmutableList<String> immutableQualifiers = ImmutableList.copyOf(qualifiers);
+ // TODO(corysmith): Address the GC thrash this creates by managing a simplified, mutable key to
+ // do the instance check.
+ FullyQualifiedName fqn = new FullyQualifiedName(pkg, immutableQualifiers, type, name);
+ // Use putIfAbsent to get the canonical instance, if there. If it isn't, putIfAbsent will
+ // return null, and we should return the current instance.
+ FullyQualifiedName cached = instanceCache.putIfAbsent(fqn, fqn);
+ if (cached == null) {
+ return fqn;
+ } else {
+ cacheHit.incrementAndGet();
+ return cached;
}
+ }
- @Override
- public String getName() {
- return resourceType.getName();
- }
+ /**
+ * Creates a new FullyQualifiedName with normalized qualifiers.
+ *
+ * @param pkg The resource package of the name. If unknown the default should be "res-auto"
+ * @param qualifiers The resource qualifiers of the name, such as "en" or "xhdpi".
+ * @param type The resource type of the name.
+ * @param name The name of the name.
+ * @return A new FullyQualifiedName.
+ */
+ static FullyQualifiedName of(
+ String pkg, List<String> qualifiers, ResourceType type, String name) {
+ return of(pkg, qualifiers, new ResourceTypeWrapper(type), name);
+ }
- @Override
- public ConcreteType getType() {
- return ConcreteType.RESOURCE_TYPE;
+ public static FullyQualifiedName fromProto(SerializeFormat.DataKey protoKey) {
+ return of(
+ protoKey.getKeyPackage(),
+ protoKey.getQualifiersList(),
+ createTypeFrom(protoKey.getResourceType()),
+ protoKey.getKeyValue());
+ }
+
+ public static void logCacheUsage(Logger logger) {
+ logger.fine(
+ String.format(
+ "Total FullyQualifiedName instance cache hits %s out of %s",
+ cacheHit.intValue(), instanceCache.size()));
+ }
+
+ /**
+ * Returns a string path representation of the FullyQualifiedName.
+ *
+ * <p>Non-values Android Resource have a well defined file layout: From the resource directory,
+ * they reside in &lt;resource type&gt;[-&lt;qualifier&gt;]/&lt;resource name&gt;[.extension]
+ *
+ * @param source The original source of the file-based resource's FullyQualifiedName
+ * @return A string representation of the FullyQualifiedName with the provided extension.
+ */
+ public String toPathString(Path source) {
+ String sourceExtension = FullyQualifiedName.Factory.getSourceExtension(source);
+ return Paths.get(
+ DASH_JOINER.join(
+ ImmutableList.<String>builder().add(type.getName()).addAll(qualifiers).build()),
+ name + sourceExtension)
+ .toString();
+ }
+
+ @Override
+ public String toPrettyString() {
+ // TODO(corysmith): Add package when we start tracking it.
+ return String.format(
+ "%s/%s",
+ DASH_JOINER.join(
+ ImmutableList.<String>builder().add(type.getName()).addAll(qualifiers).build()),
+ name);
+ }
+
+ /**
+ * Returns the string path representation of the values directory and qualifiers.
+ *
+ * <p>Certain resource types live in the "values" directory. This will calculate the directory and
+ * ensure the qualifiers are represented.
+ */
+ // TODO(corysmith): Combine this with toPathString to clean up the interface of FullyQualifiedName
+ // logically, the FullyQualifiedName should just be able to provide the relative path string for
+ // the resource.
+ public String valuesPath() {
+ return Paths.get(
+ DASH_JOINER.join(
+ ImmutableList.<String>builder().add("values").addAll(qualifiers).build()),
+ "values.xml")
+ .toString();
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public ResourceType type() {
+ if (type instanceof ResourceTypeWrapper) {
+ return ((ResourceTypeWrapper) type).resourceType;
}
+ return null;
+ }
- @Override
- public boolean isOverwritable(FullyQualifiedName fqn) {
- return !(resourceType == ResourceType.ID
- || resourceType == ResourceType.PUBLIC
- || resourceType == ResourceType.STYLEABLE);
+ public boolean isOverwritable() {
+ return type.isOverwritable(this);
+ }
+
+ /** Creates a FullyQualifiedName from this one with a different package. */
+ @CheckReturnValue
+ public FullyQualifiedName replacePackage(String newPackage) {
+ if (pkg.equals(newPackage)) {
+ return this;
}
+ return of(newPackage, qualifiers, type, name);
+ }
- @Override
- public int compareTo(Type other) {
- if (!(other instanceof ResourceTypeWrapper)) {
- return getType().compareTo(other.getType());
- }
- return resourceType.compareTo(((ResourceTypeWrapper) other).resourceType);
+ @Override
+ public int hashCode() {
+ return Objects.hash(pkg, qualifiers, type, name);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof FullyQualifiedName)) {
+ return false;
}
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof ResourceTypeWrapper)) {
- return false;
+ FullyQualifiedName other = getClass().cast(obj);
+ return Objects.equals(pkg, other.pkg)
+ && Objects.equals(type, other.type)
+ && Objects.equals(name, other.name)
+ && Objects.equals(qualifiers, other.qualifiers);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("pkg", pkg)
+ .add("qualifiers", qualifiers)
+ .add("type", type)
+ .add("name", name)
+ .toString();
+ }
+
+ @Override
+ public int compareTo(DataKey otherKey) {
+ if (!(otherKey instanceof FullyQualifiedName)) {
+ return getKeyType().compareTo(otherKey.getKeyType());
+ }
+ FullyQualifiedName other = (FullyQualifiedName) otherKey;
+ if (!pkg.equals(other.pkg)) {
+ return pkg.compareTo(other.pkg);
+ }
+ if (!type.equals(other.type)) {
+ return type.compareTo(other.type);
+ }
+ if (!name.equals(other.name)) {
+ return name.compareTo(other.name);
+ }
+ if (!qualifiers.equals(other.qualifiers)) {
+ if (qualifiers.size() != other.qualifiers.size()) {
+ return qualifiers.size() - other.qualifiers.size();
}
- ResourceTypeWrapper other = (ResourceTypeWrapper) obj;
- return Objects.equals(resourceType, other.resourceType);
+ // This works because the qualifiers are always in an ordered sequence.
+ return qualifiers.toString().compareTo(other.qualifiers.toString());
}
+ return 0;
+ }
- @Override
- public int hashCode() {
- return Objects.hashCode(resourceType);
- }
+ @Override
+ public KeyType getKeyType() {
+ return KeyType.FULL_QUALIFIED_NAME;
+ }
- @Override
- public String toString() {
- return resourceType.toString();
- }
+ @Override
+ public void serializeTo(OutputStream out, int valueSize) throws IOException {
+ toSerializedBuilder().setValueSize(valueSize).build().writeDelimitedTo(out);
+ }
+
+ public SerializeFormat.DataKey.Builder toSerializedBuilder() {
+ return SerializeFormat.DataKey.newBuilder()
+ .setKeyPackage(pkg)
+ .setResourceType(type.getName())
+ .addAllQualifiers(qualifiers)
+ .setKeyValue(name);
}
/** The non-resource {@link Type}s of a {@link FullyQualifiedName}. */
public enum VirtualType implements Type {
RESOURCES_ATTRIBUTE("<resources>", "Resources Attribute");
+ private final String name;
+ private final String displayName;
+
+ private VirtualType(String name, String displayName) {
+ this.name = name;
+ this.displayName = displayName;
+ }
+
/** Returns the enum represented by the {@code name}. */
public static VirtualType getEnum(String name) {
for (VirtualType type : values()) {
@@ -150,31 +301,23 @@ public class FullyQualifiedName implements DataKey {
/** Returns an array with all the names defined by this enum. */
public static String[] getNames() {
- VirtualType[] values = values();
- String[] names = new String[values.length];
- for (int i = values.length - 1; i >= 0; --i) {
- names[i] = values[i].getName();
- }
- return names;
- }
-
- private final String name;
- private final String displayName;
-
- private VirtualType(String name, String displayName) {
- this.name = name;
- this.displayName = displayName;
+ VirtualType[] values = values();
+ String[] names = new String[values.length];
+ for (int i = values.length - 1; i >= 0; --i) {
+ names[i] = values[i].getName();
+ }
+ return names;
}
/** Returns the resource type name. */
@Override
public String getName() {
- return name;
+ return name;
}
/** Returns a translated display name for the resource type. */
public String getDisplayName() {
- return displayName;
+ return displayName;
}
@Override
@@ -200,43 +343,120 @@ public class FullyQualifiedName implements DataKey {
@Override
public String toString() {
- return getName();
+ return getName();
}
}
- public static final String DEFAULT_PACKAGE = "res-auto";
- private static final Joiner DASH_JOINER = Joiner.on('-');
+ /** Represents the type of a {@link FullyQualifiedName}. */
+ public interface Type {
+ public String getName();
- // To save on memory, always return one instance for each FullyQualifiedName.
- // Using a HashMap to deduplicate the instances -- the key retrieves a single instance.
- private static final ConcurrentMap<FullyQualifiedName, FullyQualifiedName> instanceCache =
- new ConcurrentHashMap<>();
- private static final AtomicInteger cacheHit = new AtomicInteger(0);
+ public ConcreteType getType();
- /**
- * A factory for parsing an generating FullyQualified names with qualifiers and package.
- */
+ public boolean isOverwritable(FullyQualifiedName fqn);
+
+ public int compareTo(Type other);
+
+ @Override
+ public boolean equals(Object obj);
+
+ @Override
+ public int hashCode();
+
+ @Override
+ public String toString();
+
+ /**
+ * The category of type that a {@link Type} can be.
+ *
+ * <p><em>Note:</em> used for strict ordering of {@link FullyQualifiedName}s.
+ */
+ public enum ConcreteType {
+ RESOURCE_TYPE,
+ VIRTUAL_TYPE;
+ }
+ }
+
+ private static class ResourceTypeWrapper implements Type {
+ private final ResourceType resourceType;
+
+ public ResourceTypeWrapper(ResourceType resourceType) {
+ this.resourceType = resourceType;
+ }
+
+ @Override
+ public String getName() {
+ return resourceType.getName();
+ }
+
+ @Override
+ public ConcreteType getType() {
+ return ConcreteType.RESOURCE_TYPE;
+ }
+
+ @Override
+ public boolean isOverwritable(FullyQualifiedName fqn) {
+ return !(resourceType == ResourceType.ID
+ || resourceType == ResourceType.PUBLIC
+ || resourceType == ResourceType.STYLEABLE);
+ }
+
+ @Override
+ public int compareTo(Type other) {
+ if (!(other instanceof ResourceTypeWrapper)) {
+ return getType().compareTo(other.getType());
+ }
+ return resourceType.compareTo(((ResourceTypeWrapper) other).resourceType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ResourceTypeWrapper)) {
+ return false;
+ }
+ ResourceTypeWrapper other = (ResourceTypeWrapper) obj;
+ return Objects.equals(resourceType, other.resourceType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(resourceType);
+ }
+
+ @Override
+ public String toString() {
+ return resourceType.toString();
+ }
+ }
+
+ /** A factory for parsing an generating FullyQualified names with qualifiers and package. */
public static class Factory {
- private static final Pattern PARSING_REGEX =
- Pattern.compile("(?:(?<package>[^:]+):){0,1}(?<type>[^-/]+)(?:[^/]*)/(?<name>.+)");
public static final String INVALID_QUALIFIED_NAME_MESSAGE_NO_MATCH =
String.format(
"%%s is not a valid qualified name. "
+ "It should be in the pattern [package:]{%s}/name",
- Joiner.on(",").join(ImmutableList.<String>builder()
- .add(ResourceType.getNames())
- .add(VirtualType.getNames())
- .build()));
+ Joiner.on(",")
+ .join(
+ ImmutableList.<String>builder()
+ .add(ResourceType.getNames())
+ .add(VirtualType.getNames())
+ .build()));
public static final String INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME =
String.format(
"Could not find either resource type (%%s) or name (%%s) in %%s. "
+ "It should be in the pattern [package:]{%s}/name",
- Joiner.on(",").join(ImmutableList.<String>builder()
- .add(ResourceType.getNames())
- .add(VirtualType.getNames())
- .build()));
+ Joiner.on(",")
+ .join(
+ ImmutableList.<String>builder()
+ .add(ResourceType.getNames())
+ .add(VirtualType.getNames())
+ .build()));
public static final String INVALID_QUALIFIERS = "%s contains invalid qualifiers.";
+ private static final Pattern PARSING_REGEX =
+ Pattern.compile(
+ "(?:(?<package>[^:]+):){0,1}(?<type>[^-/]+)(?:[^/]*)/(?:(?:(?<namespace>\\{[^}]+\\}))"
+ + "|(?:(?<misplacedPackage>[^:]+):)){0,1}(?<name>.+)");
private final ImmutableList<String> qualifiers;
private final String pkg;
@@ -321,54 +541,6 @@ public class FullyQualifiedName implements DataKey {
return from(ImmutableList.copyOf(qualifiers), DEFAULT_PACKAGE);
}
- public FullyQualifiedName create(Type type, String name, String pkg) {
- return FullyQualifiedName.of(pkg, qualifiers, type, name);
- }
-
- public FullyQualifiedName create(ResourceType type, String name) {
- return create(new ResourceTypeWrapper(type), name, pkg);
- }
-
- public FullyQualifiedName create(VirtualType type, String name) {
- return create(type, name, pkg);
- }
-
- /**
- * Parses a FullyQualifiedName from a string.
- *
- * @param raw A string in the expected format from
- * [&lt;package&gt;:]&lt;ResourceType.name&gt;/&lt;resource name&gt;.
- * @throws IllegalArgumentException when the raw string is not valid qualified name.
- */
- public FullyQualifiedName parse(String raw) {
- Matcher matcher = PARSING_REGEX.matcher(raw);
- if (!matcher.matches()) {
- throw new IllegalArgumentException(
- String.format(INVALID_QUALIFIED_NAME_MESSAGE_NO_MATCH, raw));
- }
- String parsedPackage = matcher.group("package");
- Type type = createTypeFrom(matcher.group("type"));
- String name = matcher.group("name");
-
- if (type == null || name == null) {
- throw new IllegalArgumentException(
- String.format(
- INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME, type, name, raw));
- }
- return FullyQualifiedName.of(
- parsedPackage == null ? pkg : parsedPackage, qualifiers, type, name);
- }
-
- /**
- * Generates a FullyQualifiedName for a file-based resource given the source Path.
- *
- * @param sourcePath the path of the file-based resource.
- * @throws IllegalArgumentException if the file-based resource has an invalid filename
- */
- public FullyQualifiedName parse(Path sourcePath) {
- return parse(deriveRawFullyQualifiedName(sourcePath));
- }
-
private static String deriveRawFullyQualifiedName(Path source) {
if (source.getNameCount() < 2) {
throw new IllegalArgumentException(
@@ -397,225 +569,70 @@ public class FullyQualifiedName implements DataKey {
}
return "";
}
- }
- /**
- * Creates a new FullyQualifiedName with normalized qualifiers.
- *
- * @param pkg The resource package of the name. If unknown the default should be "res-auto"
- * @param qualifiers The resource qualifiers of the name, such as "en" or "xhdpi".
- * @param type The type of the name.
- * @param name The name of the name.
- * @return A new FullyQualifiedName.
- */
- public static FullyQualifiedName of(
- String pkg, List<String> qualifiers, Type type, String name) {
- checkNotNull(pkg);
- checkNotNull(qualifiers);
- checkNotNull(type);
- checkNotNull(name);
- ImmutableList<String> immutableQualifiers = ImmutableList.copyOf(qualifiers);
- // TODO(corysmith): Address the GC thrash this creates by managing a simplified, mutable key to
- // do the instance check.
- FullyQualifiedName fqn =
- new FullyQualifiedName(pkg, immutableQualifiers, type, name);
- // Use putIfAbsent to get the canonical instance, if there. If it isn't, putIfAbsent will
- // return null, and we should return the current instance.
- FullyQualifiedName cached = instanceCache.putIfAbsent(fqn, fqn);
- if (cached == null) {
- return fqn;
- } else {
- cacheHit.incrementAndGet();
- return cached;
+ public FullyQualifiedName create(Type type, String name, String pkg) {
+ return FullyQualifiedName.of(pkg, qualifiers, type, name);
}
- }
-
- /**
- * Creates a new FullyQualifiedName with normalized qualifiers.
- *
- * @param pkg The resource package of the name. If unknown the default should be "res-auto"
- * @param qualifiers The resource qualifiers of the name, such as "en" or "xhdpi".
- * @param type The resource type of the name.
- * @param name The name of the name.
- * @return A new FullyQualifiedName.
- */
- static FullyQualifiedName of(
- String pkg, List<String> qualifiers, ResourceType type, String name) {
- return of(pkg, qualifiers, new ResourceTypeWrapper(type), name);
- }
-
- public static FullyQualifiedName fromProto(SerializeFormat.DataKey protoKey) {
- return of(
- protoKey.getKeyPackage(),
- protoKey.getQualifiersList(),
- createTypeFrom(protoKey.getResourceType()),
- protoKey.getKeyValue());
- }
-
- public static void logCacheUsage(Logger logger) {
- logger.fine(
- String.format(
- "Total FullyQualifiedName instance cache hits %s out of %s",
- cacheHit.intValue(),
- instanceCache.size()));
- }
-
- private final String pkg;
- private final ImmutableList<String> qualifiers;
- private final Type type;
- private final String name;
-
- private FullyQualifiedName(
- String pkg,
- ImmutableList<String> qualifiers,
- Type type,
- String name) {
- this.pkg = pkg;
- this.qualifiers = qualifiers;
- this.type = type;
- this.name = name;
- }
-
- /**
- * Returns a string path representation of the FullyQualifiedName.
- *
- * Non-values Android Resource have a well defined file layout: From the resource directory, they
- * reside in &lt;resource type&gt;[-&lt;qualifier&gt;]/&lt;resource name&gt;[.extension]
- *
- * @param source The original source of the file-based resource's FullyQualifiedName
- * @return A string representation of the FullyQualifiedName with the provided extension.
- */
- public String toPathString(Path source) {
- String sourceExtension = FullyQualifiedName.Factory.getSourceExtension(source);
- return Paths.get(
- DASH_JOINER.join(
- ImmutableList.<String>builder()
- .add(type.getName())
- .addAll(qualifiers)
- .build()),
- name + sourceExtension)
- .toString();
- }
- @Override
- public String toPrettyString() {
- // TODO(corysmith): Add package when we start tracking it.
- return String.format(
- "%s/%s",
- DASH_JOINER.join(
- ImmutableList.<String>builder().add(type.getName()).addAll(qualifiers).build()),
- name);
- }
-
- /**
- * Returns the string path representation of the values directory and qualifiers.
- *
- * Certain resource types live in the "values" directory. This will calculate the directory and
- * ensure the qualifiers are represented.
- */
- // TODO(corysmith): Combine this with toPathString to clean up the interface of FullyQualifiedName
- // logically, the FullyQualifiedName should just be able to provide the relative path string for
- // the resource.
- public String valuesPath() {
- return Paths.get(
- DASH_JOINER.join(
- ImmutableList.<String>builder().add("values").addAll(qualifiers).build()),
- "values.xml")
- .toString();
- }
-
- public String name() {
- return name;
- }
-
- public ResourceType type() {
- if (type instanceof ResourceTypeWrapper) {
- return ((ResourceTypeWrapper) type).resourceType;
+ public FullyQualifiedName create(ResourceType type, String name) {
+ return create(new ResourceTypeWrapper(type), name, pkg);
}
- return null;
- }
- public boolean isOverwritable() {
- return type.isOverwritable(this);
- }
-
- /** Creates a FullyQualifiedName from this one with a different package. */
- @CheckReturnValue
- public FullyQualifiedName replacePackage(String newPackage) {
- if (pkg.equals(newPackage)) {
- return this;
+ public FullyQualifiedName create(ResourceType type, String name, String pkg) {
+ return create(new ResourceTypeWrapper(type), name, pkg);
}
- return of(newPackage, qualifiers, type, name);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(pkg, qualifiers, type, name);
- }
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof FullyQualifiedName)) {
- return false;
+ public FullyQualifiedName create(VirtualType type, String name) {
+ return create(type, name, pkg);
}
- FullyQualifiedName other = getClass().cast(obj);
- return Objects.equals(pkg, other.pkg)
- && Objects.equals(type, other.type)
- && Objects.equals(name, other.name)
- && Objects.equals(qualifiers, other.qualifiers);
- }
+ /**
+ * Parses a FullyQualifiedName from a string.
+ *
+ * @param raw A string in the expected format from
+ * [&lt;package&gt;:]&lt;ResourceType.name&gt;/&lt;resource name&gt;.
+ * @throws IllegalArgumentException when the raw string is not valid qualified name.
+ */
+ public FullyQualifiedName parse(String raw) {
+ Matcher matcher = PARSING_REGEX.matcher(raw);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ String.format(INVALID_QUALIFIED_NAME_MESSAGE_NO_MATCH, raw));
+ }
+ String parsedPackage =
+ firstNonNull(matcher.group("package"), matcher.group("misplacedPackage"), pkg);
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(getClass())
- .add("pkg", pkg)
- .add("qualifiers", qualifiers)
- .add("type", type)
- .add("name", name)
- .toString();
- }
+ Type type = createTypeFrom(matcher.group("type"));
+ String name =
+ matcher.group("namespace") != null
+ ? matcher.group("namespace") + matcher.group("name")
+ : matcher.group("name");
- @Override
- public int compareTo(DataKey otherKey) {
- if (!(otherKey instanceof FullyQualifiedName)) {
- return getKeyType().compareTo(otherKey.getKeyType());
- }
- FullyQualifiedName other = (FullyQualifiedName) otherKey;
- if (!pkg.equals(other.pkg)) {
- return pkg.compareTo(other.pkg);
- }
- if (!type.equals(other.type)) {
- return type.compareTo(other.type);
- }
- if (!name.equals(other.name)) {
- return name.compareTo(other.name);
- }
- if (!qualifiers.equals(other.qualifiers)) {
- if (qualifiers.size() != other.qualifiers.size()) {
- return qualifiers.size() - other.qualifiers.size();
+ if (type == null || name == null) {
+ throw new IllegalArgumentException(
+ String.format(INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME, type, name, raw));
}
- // This works because the qualifiers are always in an ordered sequence.
- return qualifiers.toString().compareTo(other.qualifiers.toString());
- }
- return 0;
- }
- @Override
- public KeyType getKeyType() {
- return KeyType.FULL_QUALIFIED_NAME;
- }
+ return FullyQualifiedName.of(parsedPackage, qualifiers, type, name);
+ }
- @Override
- public void serializeTo(OutputStream out, int valueSize) throws IOException {
- toSerializedBuilder().setValueSize(valueSize).build().writeDelimitedTo(out);
- }
+ private String firstNonNull(String... values) {
+ for (String value : values) {
+ if (value != null) {
+ return value;
+ }
+ }
+ throw new NullPointerException("Expected a nonnull value.");
+ }
- public SerializeFormat.DataKey.Builder toSerializedBuilder() {
- return SerializeFormat.DataKey.newBuilder()
- .setKeyPackage(pkg)
- .setResourceType(type.getName())
- .addAllQualifiers(qualifiers)
- .setKeyValue(name);
+ /**
+ * Generates a FullyQualifiedName for a file-based resource given the source Path.
+ *
+ * @param sourcePath the path of the file-based resource.
+ * @throws IllegalArgumentException if the file-based resource has an invalid filename
+ */
+ public FullyQualifiedName parse(Path sourcePath) {
+ return parse(deriveRawFullyQualifiedName(sourcePath));
+ }
}
}