All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.ide.common.res2.ResourceSet Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * Copyright (C) 2012 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.ide.common.res2.ResourceFile.ATTR_QUALIFIER;

import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.FolderTypeRelationship;
import com.android.resources.ResourceConstants;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.utils.ILogger;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.util.List;
import java.util.Map;

/**
 * Implementation of {@link DataSet} for {@link ResourceItem} and {@link ResourceFile}.
 *
 * This is able to detect duplicates from the same source folders (same resource coming from
 * the values folder in same or different files).
 */
public class ResourceSet extends DataSet {

    public ResourceSet(String name) {
        super(name);
    }

    @Override
    protected DataSet createSet(String name) {
        return new ResourceSet(name);
    }

    @Override
    protected ResourceFile createFileAndItems(File sourceFolder, File file, ILogger logger)
            throws MergingException {
        // get the type.
        FolderData folderData = getFolderData(file.getParentFile());

        if (folderData == null) {
            return null;
        }

        return createResourceFile(file, folderData, logger);
    }

    @Override
    protected ResourceFile createFileAndItems(@NonNull File file, @NonNull Node fileNode) {
        Attr qualifierAttr = (Attr) fileNode.getAttributes().getNamedItem(ATTR_QUALIFIER);
        String qualifier = qualifierAttr != null ? qualifierAttr.getValue() : "";

        Attr typeAttr = (Attr) fileNode.getAttributes().getNamedItem(SdkConstants.ATTR_TYPE);
        if (typeAttr == null) {
            // multi res file
            List resourceList = Lists.newArrayList();

            // loop on each node that represent a resource
            NodeList resNodes = fileNode.getChildNodes();
            for (int iii = 0, nnn = resNodes.getLength(); iii < nnn; iii++) {
                Node resNode = resNodes.item(iii);

                if (resNode.getNodeType() != Node.ELEMENT_NODE) {
                    continue;
                }

                ResourceItem r = ValueResourceParser2.getResource(resNode, file);
                if (r != null) {
                    resourceList.add(r);
                    if (r.getType() == ResourceType.DECLARE_STYLEABLE) {
                        // Need to also create ATTR items for its children
                        try {
                            ValueResourceParser2.addStyleableItems(resNode, resourceList, null, file);
                        } catch (MergingException ignored) {
                            // since we are not passing a dup map, this will never be thrown
                            assert false : file + ": " + ignored.getMessage();
                        }
                    }
                }
            }

            return new ResourceFile(file, resourceList, qualifier);

        } else {
            // single res file
            ResourceType type = ResourceType.getEnum(typeAttr.getValue());
            if (type == null) {
                return null;
            }

            Attr nameAttr = (Attr) fileNode.getAttributes().getNamedItem(ATTR_NAME);
            if (nameAttr == null) {
                return null;
            }

            ResourceItem item = new ResourceItem(nameAttr.getValue(), type, null);
            return new ResourceFile(file, item, qualifier);
        }
    }

    @Override
    protected void readSourceFolder(File sourceFolder, ILogger logger)
            throws MergingException {
        File[] folders = sourceFolder.listFiles();
        if (folders != null) {
            for (File folder : folders) {
                if (folder.isDirectory() && !isIgnored(folder)) {
                    FolderData folderData = getFolderData(folder);
                    if (folderData != null) {
                        parseFolder(sourceFolder, folder, folderData, logger);
                    }
                }
            }
        }
    }

    @Override
    protected boolean isValidSourceFile(@NonNull File sourceFolder, @NonNull File file) {
        if (!super.isValidSourceFile(sourceFolder, file)) {
            return false;
        }

        File resFolder = file.getParentFile();
        // valid files are right under a resource folder under the source folder
        return resFolder.getParentFile().equals(sourceFolder) &&
                !isIgnored(resFolder) &&
                ResourceFolderType.getFolderType(resFolder.getName()) != null;
    }

    @Override
    protected boolean handleChangedFile(@NonNull File sourceFolder, @NonNull File changedFile)
            throws MergingException {

        FolderData folderData = getFolderData(changedFile.getParentFile());
        if (folderData == null) {
            return true;
        }

        ResourceFile resourceFile = getDataFile(changedFile);

        if (resourceFile == null) {
            String message = String.format(
                    "In DataSet '%s', no data file for changedFile '%s'. "
                            + "This is an internal error in the incremental builds code; "
                            + "to work around it, try doing a full clean build.",
                    getConfigName(), changedFile.getAbsolutePath());
            throw new MergingException(message).setFile(changedFile);
        }

        //noinspection VariableNotUsedInsideIf
        if (folderData.type != null) {
            // single res file
            resourceFile.getItem().setTouched();
        } else {
            // multi res. Need to parse the file and compare the items one by one.
            ValueResourceParser2 parser = new ValueResourceParser2(changedFile);

            List parsedItems = parser.parseFile();
            Map oldItems = Maps.newHashMap(resourceFile.getItemMap());
            Map newItems  = Maps.newHashMap();

            // create a fake ResourceFile to be able to call resource.getKey();
            // It's ok because we never use this instance anyway.
            ResourceFile fakeResourceFile = new ResourceFile(changedFile, parsedItems,
                    resourceFile.getQualifiers());

            for (ResourceItem newItem : parsedItems) {
                String newKey = newItem.getKey();
                ResourceItem oldItem = oldItems.get(newKey);

                if (oldItem == null) {
                    // this is a new item
                    newItem.setTouched();
                    newItems.put(newKey, newItem);
                } else {
                    // remove it from the list of oldItems (this is to detect deletion)
                    //noinspection SuspiciousMethodCalls
                    oldItems.remove(oldItem.getKey());

                    // now compare the items
                    if (!oldItem.compareValueWith(newItem)) {
                        // if the values are different, take the values from the newItems
                        // and update the old item status.

                        oldItem.setValue(newItem);
                    }
                }
            }

            // at this point oldItems is left with the deleted items.
            // just update their status to removed.
            for (ResourceItem deletedItem : oldItems.values()) {
                deletedItem.setRemoved();
            }

            // Now we need to add the new items to the resource file and the main map
            resourceFile.addItems(newItems.values());
            for (Map.Entry entry : newItems.entrySet()) {
                addItem(entry.getValue(), entry.getKey());
            }
        }

       return true;
    }

    /**
     * Reads the content of a typed resource folder (sub folder to the root of res folder), and
     * loads the resources from it.
     *
     *
     * @param sourceFolder the main res folder
     * @param folder the folder to read.
     * @param folderData the folder Data
     * @param logger a logger object
     *
     * @throws MergingException if something goes wrong
     */
    private void parseFolder(File sourceFolder, File folder, FolderData folderData, ILogger logger)
            throws MergingException {
        File[] files = folder.listFiles();
        if (files != null && files.length > 0) {
            for (File file : files) {
                if (!file.isFile() || isIgnored(file)) {
                    continue;
                }

                ResourceFile resourceFile = createResourceFile(file, folderData, logger);
                if (resourceFile != null) {
                    processNewDataFile(sourceFolder, resourceFile, true /*setTouched*/);
                }
            }
        }
    }

    private static ResourceFile createResourceFile(File file, FolderData folderData, ILogger logger)
            throws MergingException {
        if (folderData.type != null) {
            int pos;// get the resource name based on the filename
            String name = file.getName();
            pos = name.indexOf('.');
            if (pos >= 0) {
                name = name.substring(0, pos);
            }

            return new ResourceFile(
                    file,
                    new ResourceItem(name, folderData.type, null),
                    folderData.qualifiers);
        } else {
            try {
                ValueResourceParser2 parser = new ValueResourceParser2(file);
                List items = parser.parseFile();

                return new ResourceFile(file, items, folderData.qualifiers);
            } catch (MergingException e) {
                e.setFile(file);
                logger.error(e, "Failed to parse %s", file.getAbsolutePath());
                throw e;
            }
        }
    }

    /**
     * temp structure containing a qualifier string and a {@link com.android.resources.ResourceType}.
     */
    private static class FolderData {
        String qualifiers = "";
        ResourceType type = null;
        ResourceFolderType folderType = null;
    }

    /**
     * Returns a FolderData for the given folder
     * @param folder the folder.
     * @return the FolderData object.
     */
    @Nullable
    private static FolderData getFolderData(File folder) {
        FolderData fd = new FolderData();

        String folderName = folder.getName();
        int pos = folderName.indexOf(ResourceConstants.RES_QUALIFIER_SEP);
        if (pos != -1) {
            fd.folderType = ResourceFolderType.getTypeByName(folderName.substring(0, pos));
            if (fd.folderType == null) {
                return null;
            }

            FolderConfiguration folderConfiguration = FolderConfiguration.getConfigForFolder(folderName);
            if (folderConfiguration == null) {
                return null;
            }

            // normalize it
            folderConfiguration.normalize();

            // get the qualifier portion from the folder config.
            // the returned string starts with "-" so we remove that.
            fd.qualifiers = folderConfiguration.getUniqueKey().substring(1);

        } else {
            fd.folderType = ResourceFolderType.getTypeByName(folderName);
        }

        if (fd.folderType != null && fd.folderType != ResourceFolderType.VALUES) {
            fd.type = FolderTypeRelationship.getRelatedResourceTypes(fd.folderType).get(0);
        }

        return fd;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy