From 3e5ec96983f98ea4062ce0ca42df43e484777d1c Mon Sep 17 00:00:00 2001 From: corysmith Date: Tue, 29 Aug 2017 23:04:18 +0200 Subject: Add support for the /: method of declaring resources. RELNOTES: None PiperOrigin-RevId: 166899690 --- .../devtools/build/android/FullyQualifiedName.java | 753 +++++++++++---------- 1 file changed, 385 insertions(+), 368 deletions(-) (limited to 'src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java') 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. + *

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. - * - *

- * Note: 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 instanceCache = + new ConcurrentHashMap<>(); + private static final AtomicInteger cacheHit = new AtomicInteger(0); + private final String pkg; + private final ImmutableList 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 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 qualifiers, Type type, String name) { + checkNotNull(pkg); + checkNotNull(qualifiers); + checkNotNull(type); + checkNotNull(name); + ImmutableList 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 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. + * + *

Non-values Android Resource have a well defined file layout: From the resource directory, + * they reside in <resource type>[-<qualifier>]/<resource name>[.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.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.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.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 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 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. + * + *

Note: 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("(?:(?[^:]+):){0,1}(?[^-/]+)(?:[^/]*)/(?.+)"); 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.builder() - .add(ResourceType.getNames()) - .add(VirtualType.getNames()) - .build())); + Joiner.on(",") + .join( + ImmutableList.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.builder() - .add(ResourceType.getNames()) - .add(VirtualType.getNames()) - .build())); + Joiner.on(",") + .join( + ImmutableList.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( + "(?:(?[^:]+):){0,1}(?[^-/]+)(?:[^/]*)/(?:(?:(?\\{[^}]+\\}))" + + "|(?:(?[^:]+):)){0,1}(?.+)"); private final ImmutableList qualifiers; private final String pkg; @@ -293,80 +513,32 @@ public class FullyQualifiedName implements DataKey { // entire subtrees. Builder builder = ImmutableList.builder(); addIfNotNull(config.getCountryCodeQualifier(), builder); - addIfNotNull(config.getNetworkCodeQualifier(), builder); - if (transformedLocaleQualifiers.isEmpty()) { - addIfNotNull(config.getLocaleQualifier(), builder); - } else { - builder.addAll(transformedLocaleQualifiers); - } - // index 3 is past the country code, network code, and locale indices. - for (int i = 3; i < FolderConfiguration.getQualifierCount(); ++i) { - addIfNotNull(config.getQualifier(i), builder); - } - return builder.build(); - } - - private static void addIfNotNull( - ResourceQualifier qualifier, ImmutableList.Builder builder) { - if (qualifier != null) { - builder.add(qualifier.getFolderSegment()); - } - } - - public static Factory from(List qualifiers, String pkg) { - return new Factory(ImmutableList.copyOf(qualifiers), pkg); - } - - public static Factory from(List qualifiers) { - 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 - * [<package>:]<ResourceType.name>/<resource name>. - * @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)); + addIfNotNull(config.getNetworkCodeQualifier(), builder); + if (transformedLocaleQualifiers.isEmpty()) { + addIfNotNull(config.getLocaleQualifier(), builder); + } else { + builder.addAll(transformedLocaleQualifiers); } - String parsedPackage = matcher.group("package"); - Type type = createTypeFrom(matcher.group("type")); - String name = matcher.group("name"); + // index 3 is past the country code, network code, and locale indices. + for (int i = 3; i < FolderConfiguration.getQualifierCount(); ++i) { + addIfNotNull(config.getQualifier(i), builder); + } + return builder.build(); + } - if (type == null || name == null) { - throw new IllegalArgumentException( - String.format( - INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME, type, name, raw)); + private static void addIfNotNull( + ResourceQualifier qualifier, ImmutableList.Builder builder) { + if (qualifier != null) { + builder.add(qualifier.getFolderSegment()); } - 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)); + public static Factory from(List qualifiers, String pkg) { + return new Factory(ImmutableList.copyOf(qualifiers), pkg); + } + + public static Factory from(List qualifiers) { + return from(ImmutableList.copyOf(qualifiers), DEFAULT_PACKAGE); } private static String deriveRawFullyQualifiedName(Path source) { @@ -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 qualifiers, Type type, String name) { - checkNotNull(pkg); - checkNotNull(qualifiers); - checkNotNull(type); - checkNotNull(name); - ImmutableList 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 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 qualifiers; - private final Type type; - private final String name; - - private FullyQualifiedName( - String pkg, - ImmutableList 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 <resource type>[-<qualifier>]/<resource name>[.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.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.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.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 + * [<package>:]<ResourceType.name>/<resource name>. + * @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)); + } } } -- cgit v1.2.3