com.android.tools.idea.rendering.MultiResourceRepository.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.rendering.MultiResourceRepository.java

Source

/*
 * 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.tools.idea.rendering;

import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.res2.ResourceItem;
import com.android.resources.ResourceType;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.List;
import java.util.Map;

@SuppressWarnings({ "deprecation", // Deprecated com.android.util.Pair is required by ProjectCallback interface
        "SynchronizeOnThis" })
public abstract class MultiResourceRepository extends LocalResourceRepository {
    protected List<? extends LocalResourceRepository> myChildren;
    private long[] myModificationCounts;
    private Map<ResourceType, ListMultimap<String, ResourceItem>> myItems = Maps.newEnumMap(ResourceType.class);
    private final Map<ResourceType, ListMultimap<String, ResourceItem>> myCachedTypeMaps = Maps
            .newEnumMap(ResourceType.class);
    private Map<String, DataBindingInfo> myDataBindingResourceFiles = Maps.newHashMap();
    private long myDataBindingResourceFilesModificationCount = Long.MIN_VALUE;

    MultiResourceRepository(@NotNull String displayName,
            @NotNull List<? extends LocalResourceRepository> children) {
        super(displayName);
        setChildren(children);
    }

    protected void setChildren(@NotNull List<? extends LocalResourceRepository> children) {
        if (myChildren != null) {
            for (int i = myChildren.size() - 1; i >= 0; i--) {
                LocalResourceRepository resources = myChildren.get(i);
                resources.removeParent(this);
            }
            if (myChildren.size() == 1) {
                myGeneration = Math.max(myChildren.get(0).getModificationCount(), myGeneration);
            }
        }
        myChildren = children;
        myModificationCounts = new long[children.size()];
        if (children.size() == 1) {
            // Make sure that the modification count of the child and the parent are same. This is
            // done so that we can return child's modification count, instead of ours.
            LocalResourceRepository child = children.get(0);
            long modCount = Math.max(child.getModificationCount(), myGeneration);
            child.myGeneration = modCount + 1;
            myGeneration = modCount; // it's incremented below.
        }
        for (int i = myChildren.size() - 1; i >= 0; i--) {
            LocalResourceRepository resources = myChildren.get(i);
            resources.addParent(this);
            myModificationCounts[i] = resources.getModificationCount();
        }
        myGeneration++;
        clearCache();
        invalidateItemCaches();
    }

    private void clearCache() {
        myItems = null;
        synchronized (this) {
            myCachedTypeMaps.clear();
        }
    }

    public List<? extends LocalResourceRepository> getChildren() {
        return myChildren;
    }

    @Override
    public long getModificationCount() {
        if (myChildren.size() == 1) {
            return myChildren.get(0).getModificationCount();
        }

        // See if any of the delegates have changed
        boolean changed = false;
        for (int i = myChildren.size() - 1; i >= 0; i--) {
            LocalResourceRepository resources = myChildren.get(i);
            long rev = resources.getModificationCount();
            if (rev != myModificationCounts[i]) {
                myModificationCounts[i] = rev;
                changed = true;
            }
        }
        if (changed) {
            myGeneration++;
        }

        return myGeneration;
    }

    @Nullable
    @Override
    public DataBindingInfo getDataBindingInfoForLayout(String layoutName) {
        for (LocalResourceRepository child : myChildren) {
            DataBindingInfo info = child.getDataBindingInfoForLayout(layoutName);
            if (info != null) {
                return info;
            }
        }
        return null;
    }

    @NotNull
    @Override
    public Map<String, DataBindingInfo> getDataBindingResourceFiles() {
        long modificationCount = getModificationCount();
        if (myDataBindingResourceFilesModificationCount == modificationCount) {
            return myDataBindingResourceFiles;
        }
        Map<String, DataBindingInfo> selected = Maps.newHashMap();
        for (LocalResourceRepository child : myChildren) {
            Map<String, DataBindingInfo> childFiles = child.getDataBindingResourceFiles();
            if (childFiles != null) {
                selected.putAll(childFiles);
            }
        }
        myDataBindingResourceFiles = Collections.unmodifiableMap(selected);
        myDataBindingResourceFilesModificationCount = modificationCount;
        return myDataBindingResourceFiles;
    }

    @NonNull
    @Override
    protected Map<ResourceType, ListMultimap<String, ResourceItem>> getMap() {
        if (myItems == null) {
            if (myChildren.size() == 1) {
                myItems = myChildren.get(0).getItems();
            } else {
                Map<ResourceType, ListMultimap<String, ResourceItem>> map = Maps.newEnumMap(ResourceType.class);
                for (ResourceType type : ResourceType.values()) {
                    map.put(type, getMap(type, false)); // should pass create is true, but as described below we interpret this differently
                }
                myItems = map;
            }
        }

        return myItems;
    }

    @Nullable
    @Override
    protected ListMultimap<String, ResourceItem> getMap(ResourceType type, boolean create) {
        // Should I assert !create here? If we try to manipulate the cache it won't work right...
        synchronized (this) {
            ListMultimap<String, ResourceItem> map = myCachedTypeMaps.get(type);
            if (map != null) {
                return map;
            }
        }

        if (myChildren.size() == 1) {
            LocalResourceRepository child = myChildren.get(0);
            if (child instanceof MultiResourceRepository) {
                return ((MultiResourceRepository) child).getMap(type);
            }
            return child.getItems().get(type);
        }

        ListMultimap<String, ResourceItem> map = ArrayListMultimap.create();

        // Merge all items of the given type
        for (int i = myChildren.size() - 1; i >= 0; i--) {
            LocalResourceRepository resources = myChildren.get(i);
            Map<ResourceType, ListMultimap<String, ResourceItem>> items = resources.getItems();
            ListMultimap<String, ResourceItem> m = items.get(type);
            if (m == null) {
                continue;
            }

            // TODO: Start with JUST the first map here (which often contains most of the keys) and then
            // only merge in 1...n
            for (ResourceItem item : m.values()) {
                String name = item.getName();
                if (map.containsKey(name) && item.getType() != ResourceType.ID) {
                    // The item already exists in this map; only add if there isn't an item with the
                    // same qualifiers (and it's not an id; id's are allowed to be defined in multiple
                    // places even with the same qualifiers)
                    String qualifiers = item.getQualifiers();
                    boolean contains = false;
                    List<ResourceItem> list = map.get(name);
                    assert list != null;
                    for (ResourceItem existing : list) {
                        if (qualifiers.equals(existing.getQualifiers())) {
                            contains = true;
                            break;
                        }
                    }
                    if (!contains) {
                        map.put(name, item);
                    }
                } else {
                    map.put(name, item);
                }
            }
        }

        synchronized (this) {
            myCachedTypeMaps.put(type, map);
        }

        return map;
    }

    @NonNull
    @Override
    protected ListMultimap<String, ResourceItem> getMap(ResourceType type) {
        return super.getMap(type);
    }

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

    @Override
    public void dispose() {
        for (int i = myChildren.size() - 1; i >= 0; i--) {
            LocalResourceRepository resources = myChildren.get(i);
            resources.removeParent(this);
            resources.dispose();
        }
    }

    /**
     * Notifies this delegating repository that the given dependent repository has invalidated
     * resources of the given types (empty means all)
     */
    public void invalidateCache(@NotNull LocalResourceRepository repository, @Nullable ResourceType... types) {
        assert myChildren.contains(repository) : repository;

        synchronized (this) {
            if (types == null || types.length == 0) {
                myCachedTypeMaps.clear();
            } else {
                for (ResourceType type : types) {
                    myCachedTypeMaps.remove(type);
                }
            }
        }
        myItems = null;
        myGeneration++;

        invalidateItemCaches(types);
    }

    @Override
    @VisibleForTesting
    public boolean isScanPending(@NonNull PsiFile psiFile) {
        assert ApplicationManager.getApplication().isUnitTestMode();
        for (int i = myChildren.size() - 1; i >= 0; i--) {
            LocalResourceRepository resources = myChildren.get(i);
            if (resources.isScanPending(psiFile)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void sync() {
        super.sync();

        for (int i = myChildren.size() - 1; i >= 0; i--) {
            LocalResourceRepository resources = myChildren.get(i);
            resources.sync();
        }
    }

    @VisibleForTesting
    int getChildCount() {
        return myChildren.size();
    }
}