Package com.android.ide.common.res2

Source Code of com.android.ide.common.res2.AbstractResourceRepository$RepositoryMerger

/*
* 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.res2;

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.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceUrl;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.ResourceType;
import com.google.common.base.Splitter;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilderFactory;

public abstract class AbstractResourceRepository {

    private final boolean mFramework;

    private class RepositoryMerger implements MergeConsumer<ResourceItem> {

        @Override
        public void start(@NonNull DocumentBuilderFactory factory)
                throws ConsumerException {
        }

        @Override
        public void end() throws ConsumerException {
        }

        @Override
        public void addItem(@NonNull ResourceItem item) throws ConsumerException {
            if (item.isTouched()) {
                AbstractResourceRepository.this.addItem(item);
            }
        }

        @Override
        public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
                throws ConsumerException {
            AbstractResourceRepository.this.removeItem(removedItem);
        }

        @Override
        public boolean ignoreItemInMerge(ResourceItem item) {
            // we never ignore any item.
            return false;
        }
    }

    public AbstractResourceRepository(boolean isFramework) {
        mFramework = isFramework;
    }

    public boolean isFramework() {
        return mFramework;
    }

    @NonNull
    public MergeConsumer<ResourceItem> createMergeConsumer() {
        return new RepositoryMerger();
    }

    @NonNull
    protected abstract Map<ResourceType, ListMultimap<String, ResourceItem>> getMap();

    @Nullable
    protected abstract ListMultimap<String, ResourceItem> getMap(ResourceType type, boolean create);

    @NonNull
    protected ListMultimap<String, ResourceItem> getMap(ResourceType type) {
        //noinspection ConstantConditions
        return getMap(type, true); // Won't return null if create is false
    }

    @NonNull
    public Map<ResourceType, ListMultimap<String, ResourceItem>> getItems() {
        return getMap();
    }

    /** Lock used to protect map access */
    protected static final Object ITEM_MAP_LOCK = new Object();

    // TODO: Rename to getResourceItemList?
    @Nullable
    public List<ResourceItem> getResourceItem(@NonNull ResourceType resourceType,
            @NonNull String resourceName) {
        synchronized (ITEM_MAP_LOCK) {
            ListMultimap<String, ResourceItem> map = getMap(resourceType, false);

            if (map != null) {
                return map.get(resourceName);
            }
        }

        return null;
    }

    @NonNull
    public Collection<String> getItemsOfType(@NonNull ResourceType type) {
        synchronized (ITEM_MAP_LOCK) {
            Multimap<String, ResourceItem> map = getMap(type, false);
            if (map == null) {
                return Collections.emptyList();
            }
            return Collections.unmodifiableCollection(map.keySet());
        }
    }

    /**
     * Returns true if this resource repository contains a resource of the given
     * name.
     *
     * @param url the resource URL
     * @return true if the resource is known
     */
    public boolean hasResourceItem(@NonNull String url) {
        // Handle theme references
        if (url.startsWith(PREFIX_THEME_REF)) {
            String remainder = url.substring(PREFIX_THEME_REF.length());
            if (url.startsWith(ATTR_REF_PREFIX)) {
                url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
                return hasResourceItem(url);
            }
            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;
                return hasResourceItem(url);
            } else {
                int slash = url.indexOf('/');
                if (slash == -1) {
                    url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
                    return hasResourceItem(url);
                }
            }
        }

        if (!url.startsWith(PREFIX_RESOURCE_REF)) {
            return false;
        }

        assert url.startsWith("@") || url.startsWith("?") : url;

        int typeEnd = url.indexOf('/', 1);
        if (typeEnd != -1) {
            int nameBegin = typeEnd + 1;

            // Skip @ and @+
            int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$

            int colon = url.lastIndexOf(':', typeEnd);
            if (colon != -1) {
                typeBegin = colon + 1;
            }
            String typeName = url.substring(typeBegin, typeEnd);
            ResourceType type = ResourceType.getEnum(typeName);
            if (type != null) {
                String name = url.substring(nameBegin);
                return hasResourceItem(type, name);
            }
        }

        return false;
    }

    /**
     * Returns true if this resource repository contains a resource of the given
     * name.
     *
     * @param resourceType the type of resource to look up
     * @param resourceName the name of the resource
     * @return true if the resource is known
     */
    public boolean hasResourceItem(@NonNull ResourceType resourceType,
            @NonNull String resourceName) {
        synchronized (ITEM_MAP_LOCK) {
            ListMultimap<String, ResourceItem> map = getMap(resourceType, false);

            if (map != null) {
                List<ResourceItem> itemList = map.get(resourceName);
                return itemList != null && !itemList.isEmpty();
            }
        }

        return false;
    }

    /**
     * Returns whether the repository has resources of a given {@link ResourceType}.
     * @param resourceType the type of resource to check.
     * @return true if the repository contains resources of the given type, false otherwise.
     */
    public boolean hasResourcesOfType(@NonNull ResourceType resourceType) {
        synchronized (ITEM_MAP_LOCK) {
            ListMultimap<String, ResourceItem> map = getMap(resourceType, false);
            return map != null && !map.isEmpty();
        }
    }

    @NonNull
    public List<ResourceType> getAvailableResourceTypes() {
        synchronized (ITEM_MAP_LOCK) {
            return Lists.newArrayList(getMap().keySet());
        }
    }

    /**
     * Returns the {@link ResourceFile} matching the given name, {@link ResourceType} and
     * configuration.
     * <p/>
     * This only works with files generating one resource named after the file
     * (for instance, layouts, bitmap based drawable, xml, anims).
     *
     * @param name the resource name
     * @param type the folder type search for
     * @param config the folder configuration to match for
     * @return the matching file or <code>null</code> if no match was found.
     */
    @Nullable
    public ResourceFile getMatchingFile(
            @NonNull String name,
            @NonNull ResourceType type,
            @NonNull FolderConfiguration config) {

        synchronized (ITEM_MAP_LOCK) {
            ListMultimap<String, ResourceItem> typeItems = getMap(type, false);
            if (typeItems == null) {
                return null;
            }

            for (int depth = 0; depth < MAX_RESOURCE_INDIRECTION; depth++) {
                List<ResourceItem> matchingItems = typeItems.get(name);
                if (matchingItems == null || matchingItems.isEmpty()) {
                    return null;
                }

                ResourceItem match = (ResourceItem) config.findMatchingConfigurable(matchingItems);
                if (match != null) {
                    ResourceValue resourceValue = match.getResourceValue(isFramework());
                    if (resourceValue != null) {
                        String value = resourceValue.getValue();
                        if (value != null && value.startsWith(PREFIX_RESOURCE_REF)) {
                            ResourceUrl url = ResourceUrl.parse(value);
                            if (url != null && url.type == type
                                    && url.framework == isFramework()) {
                                name = url.name;
                                continue;
                            }
                        }
                    }

                    return match.getSource();
                } else {
                    return null;
                }
            }
        }

        return null;
    }

    /**
     * Returns the resources values matching a given {@link FolderConfiguration}.
     *
     * @param referenceConfig the configuration that each value must match.
     * @return a map with guaranteed to contain an entry for each {@link ResourceType}
     */
    @NonNull
    public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
            @NonNull FolderConfiguration referenceConfig) {
        Map<ResourceType, Map<String, ResourceValue>> map = Maps.newEnumMap(ResourceType.class);

        synchronized (ITEM_MAP_LOCK) {
            Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap = getMap();
            for (ResourceType key : ResourceType.values()) {
                // get the local results and put them in the map
                map.put(key, getConfiguredResources(itemMap, key, referenceConfig));
            }
        }

        return map;
    }

    /**
     * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
     * <p/>The values returned are taken from the resource files best matching a given
     * {@link FolderConfiguration}.
     * @param type the type of the resources.
     * @param referenceConfig the configuration to best match.
     */
    @NonNull
    public Map<String, ResourceValue> getConfiguredResources(
            @NonNull ResourceType type,
            @NonNull FolderConfiguration referenceConfig) {
        return getConfiguredResources(getMap(), type, referenceConfig);
    }

    @NonNull
    public Map<String, ResourceValue> getConfiguredResources(
            @NonNull Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap,
            @NonNull ResourceType type,
            @NonNull FolderConfiguration referenceConfig) {
        // get the resource item for the given type
        ListMultimap<String, ResourceItem> items = itemMap.get(type);
        if (items == null) {
            return Maps.newHashMap();
        }

        Set<String> keys = items.keySet();

        // create the map
        Map<String, ResourceValue> map = Maps.newHashMapWithExpectedSize(keys.size());

        for (String key : keys) {
            List<ResourceItem> keyItems = items.get(key);

            // look for the best match for the given configuration
            // the match has to be of type ResourceFile since that's what the input list contains
            ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
            if (match != null) {
                ResourceValue value = match.getResourceValue(mFramework);
                if (value != null) {
                    map.put(match.getName(), value);
                }
            }
        }

        return map;
    }

    @Nullable
    public ResourceValue getConfiguredValue(
            @NonNull ResourceType type,
            @NonNull String name,
            @NonNull FolderConfiguration referenceConfig) {
        // get the resource item for the given type
        ListMultimap<String, ResourceItem> items = getMap(type, false);
        if (items == null) {
            return null;
        }

        List<ResourceItem> keyItems = items.get(name);
        if (keyItems == null) {
            return null;
        }

        // look for the best match for the given configuration
        // the match has to be of type ResourceFile since that's what the input list contains
        ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
        return match != null ? match.getResourceValue(mFramework) : null;
    }

    private void addItem(@NonNull ResourceItem item) {
        synchronized (ITEM_MAP_LOCK) {
            ListMultimap<String, ResourceItem> map = getMap(item.getType());
            if (!map.containsValue(item)) {
                map.put(item.getName(), item);
            }
        }
    }

    private void removeItem(@NonNull ResourceItem removedItem) {
        synchronized (ITEM_MAP_LOCK) {
            Multimap<String, ResourceItem> map = getMap(removedItem.getType(), false);
            if (map != null) {
                map.remove(removedItem.getName(), removedItem);
            }
        }
    }

    /**
     * Returns the sorted list of languages used in the resources.
     */
    @NonNull
    public SortedSet<String> getLanguages() {
        SortedSet<String> set = new TreeSet<String>();

        // As an optimization we could just look for values since that's typically where
        // the languages are defined -- not on layouts, menus, etc -- especially if there
        // are no translations for it
        Set<String> qualifiers = Sets.newHashSet();

        synchronized (ITEM_MAP_LOCK) {
            for (ListMultimap<String, ResourceItem> map : getMap().values()) {
                for (ResourceItem item : map.values()) {
                    qualifiers.add(item.getQualifiers());
                }
            }
        }

        Splitter splitter = Splitter.on('-');
        for (String s : qualifiers) {
            for (String qualifier : splitter.split(s)) {
                if (qualifier.length() == 2 && Character.isLetter(qualifier.charAt(0))
                        && Character.isLetter(qualifier.charAt(1))) {
                    set.add(qualifier);
                }
            }
        }

        return set;
    }

    /**
     * Returns the sorted list of regions used in the resources with the given language.
     * @param currentLanguage the current language the region must be associated with.
     */
    @NonNull
    public SortedSet<String> getRegions(@NonNull String currentLanguage) {
        SortedSet<String> set = new TreeSet<String>();

        // As an optimization we could just look for values since that's typically where
        // the languages are defined -- not on layouts, menus, etc -- especially if there
        // are no translations for it
        Set<String> qualifiers = Sets.newHashSet();
        synchronized (ITEM_MAP_LOCK) {
            for (ListMultimap<String, ResourceItem> map : getMap().values()) {
                for (ResourceItem item : map.values()) {
                    qualifiers.add(item.getQualifiers());
                }
            }
        }

        Splitter splitter = Splitter.on('-');
        for (String s : qualifiers) {
            boolean rightLanguage = false;
            for (String qualifier : splitter.split(s)) {
                if (currentLanguage.equals(qualifier)) {
                    rightLanguage = true;
                } else if (rightLanguage
                        && qualifier.length() == 3
                        && qualifier.charAt(0) == 'r'
                        && Character.isUpperCase(qualifier.charAt(1))
                        && Character.isUpperCase(qualifier.charAt(2))) {
                    set.add(qualifier.substring(1));
                }
            }
        }

        return set;
    }

    public void clear() {
        getMap().clear();
    }
}
TOP

Related Classes of com.android.ide.common.res2.AbstractResourceRepository$RepositoryMerger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.