| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * 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.android.ide.common.resources; |
| |
| import static com.android.SdkConstants.ANDROID_NS_NAME; |
| import static com.android.SdkConstants.ATTR_REF_PREFIX; |
| import static com.android.SdkConstants.PREFIX_RESOURCE_REF; |
| import static com.android.SdkConstants.PREFIX_THEME_REF; |
| import static com.android.SdkConstants.RESOURCE_CLZ_ATTR; |
| import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY; |
| import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL; |
| import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.common.rendering.api.ResourceValue; |
| import com.android.resources.ResourceType; |
| |
| /** |
| * A {@linkplain ResourceUrl} represents a parsed resource url such as {@code @string/foo} or |
| * {@code ?android:attr/bar} |
| */ |
| public class ResourceUrl { |
| /** Type of resource */ |
| @NonNull public final ResourceType type; |
| |
| /** Name of resource */ |
| @NonNull public final String name; |
| |
| /** If true, the resource is in the android: framework */ |
| public final boolean framework; |
| |
| /** Whether an id resource is of the form {@code @+id} rather than just {@code @id} */ |
| public final boolean create; |
| |
| /** Whether this is a theme resource reference */ |
| public final boolean theme; |
| |
| private ResourceUrl(@NonNull ResourceType type, @NonNull String name, |
| boolean framework, boolean create, boolean theme) { |
| this.type = type; |
| this.name = name; |
| this.framework = framework; |
| this.create = create; |
| this.theme = theme; |
| } |
| |
| /** |
| * Creates a new resource URL. Normally constructed via {@link #parse(String)}. |
| * |
| * @param type the resource type |
| * @param name the name |
| * @param framework whether it's a framework resource |
| * @param create if it's an id resource, whether it's of the form {@code @+id} |
| */ |
| public static ResourceUrl create(@NonNull ResourceType type, @NonNull String name, |
| boolean framework, boolean create) { |
| return new ResourceUrl(type, name, framework, create, false); |
| } |
| |
| public static ResourceUrl create(@NonNull ResourceValue value) { |
| return create(value.getResourceType(), value.getName(), value.isFramework(), false); |
| } |
| |
| /** |
| * Return the resource type of the given url, and the resource name |
| * |
| * @param url the resource url to be parsed |
| * @return a pair of the resource type and the resource name |
| */ |
| @Nullable |
| public static ResourceUrl parse(@NonNull String url) { |
| return parse(url, false); |
| } |
| |
| /** |
| * Return the resource type of the given url, and the resource name. |
| * |
| * @param url the resource url to be parsed |
| * @param forceFramework force the returned value to be a framework resource. |
| * @return a pair of the resource type and the resource name |
| */ |
| @Nullable |
| public static ResourceUrl parse(@NonNull String url, boolean forceFramework) { |
| boolean isTheme = false; |
| // Handle theme references |
| if (url.startsWith(PREFIX_THEME_REF)) { |
| isTheme = true; |
| String remainder = url.substring(PREFIX_THEME_REF.length()); |
| if (url.startsWith(ATTR_REF_PREFIX)) { |
| url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length()); |
| } else { |
| int colon = url.indexOf(':'); |
| if (colon != -1) { |
| // Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig |
| if (remainder.indexOf('/', colon) == -1) { |
| remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/' |
| + remainder.substring(colon); |
| } |
| url = PREFIX_RESOURCE_REF + remainder; |
| } else { |
| int slash = url.indexOf('/'); |
| if (slash == -1) { |
| url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder; |
| } |
| } |
| } |
| } |
| |
| if (!url.startsWith(PREFIX_RESOURCE_REF) || isNullOrEmpty(url)) { |
| return null; |
| } |
| |
| int typeEnd = url.indexOf('/', 1); |
| if (typeEnd == -1) { |
| return null; |
| } |
| int nameBegin = typeEnd + 1; |
| |
| // Skip @ and @+ |
| boolean create = url.startsWith("@+"); |
| int typeBegin = create ? 2 : 1; |
| |
| int colon = url.lastIndexOf(':', typeEnd); |
| boolean framework = forceFramework; |
| if (colon != -1) { |
| if (url.startsWith(ANDROID_NS_NAME, typeBegin)) { |
| framework = true; |
| } |
| typeBegin = colon + 1; |
| } |
| String typeName = url.substring(typeBegin, typeEnd); |
| ResourceType type = ResourceType.getEnum(typeName); |
| if (type == null) { |
| return null; |
| } |
| String name = url.substring(nameBegin); |
| return new ResourceUrl(type, name, framework, create, isTheme); |
| } |
| |
| /** Returns if the resource url is @null, @empty or @undefined. */ |
| public static boolean isNullOrEmpty(@NonNull String url) { |
| return url.equals(REFERENCE_NULL) || url.equals(REFERENCE_EMPTY) || |
| url.equals(REFERENCE_UNDEFINED); |
| } |
| |
| /** |
| * Checks whether this resource has a valid name. Used when parsing data that isn't |
| * necessarily known to be a valid resource; for example, "?attr/hello world" |
| */ |
| public boolean hasValidName() { |
| // Make sure it looks like a resource name; if not, it could just be a string |
| // which starts with a ?, etc. |
| if (name.isEmpty()) { |
| return false; |
| } |
| |
| if (!Character.isJavaIdentifierStart(name.charAt(0))) { |
| return false; |
| } |
| for (int i = 1, n = name.length(); i < n; i++) { |
| char c = name.charAt(i); |
| if (!Character.isJavaIdentifierPart(c) && c != '.') { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(theme ? PREFIX_THEME_REF : PREFIX_RESOURCE_REF); |
| if (create) { |
| sb.append('+'); |
| } |
| if (framework) { |
| sb.append(ANDROID_NS_NAME); |
| sb.append(':'); |
| } |
| sb.append(type.getName()); |
| sb.append('/'); |
| sb.append(name); |
| return sb.toString(); |
| } |
| |
| @SuppressWarnings("RedundantIfStatement") |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| ResourceUrl that = (ResourceUrl) o; |
| |
| if (create != that.create) { |
| return false; |
| } |
| if (framework != that.framework) { |
| return false; |
| } |
| if (!name.equals(that.name)) { |
| return false; |
| } |
| if (type != that.type) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = type.hashCode(); |
| result = 31 * result + name.hashCode(); |
| result = 31 * result + (framework ? 1 : 0); |
| result = 31 * result + (create ? 1 : 0); |
| return result; |
| } |
| } |