aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java1111
1 files changed, 0 insertions, 1111 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
deleted file mode 100644
index 7bab914e5..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
+++ /dev/null
@@ -1,1111 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.eclipse.adt.internal.editors.layout.gle2;
-
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.EXT_XML;
-import static com.android.SdkConstants.FD_RESOURCES;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.SdkConstants.VIEW_FRAGMENT;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS;
-import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
-import static com.android.resources.ResourceType.LAYOUT;
-import static org.eclipse.core.resources.IResourceDelta.ADDED;
-import static org.eclipse.core.resources.IResourceDelta.CHANGED;
-import static org.eclipse.core.resources.IResourceDelta.CONTENT;
-import static org.eclipse.core.resources.IResourceDelta.REMOVED;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.ide.common.resources.ResourceFile;
-import com.android.ide.common.resources.ResourceFolder;
-import com.android.ide.common.resources.ResourceItem;
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
-import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager.IResourceListener;
-import com.android.ide.eclipse.adt.io.IFileWrapper;
-import com.android.io.IAbstractFile;
-import com.android.resources.ResourceType;
-
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.QualifiedName;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The include finder finds other XML files that are including a given XML file, and does
- * so efficiently (caching results across IDE sessions etc).
- */
-@SuppressWarnings("restriction") // XML model
-public class IncludeFinder {
- /** Qualified name for the per-project persistent property include-map */
- private final static QualifiedName CONFIG_INCLUDES = new QualifiedName(AdtPlugin.PLUGIN_ID,
- "includes");//$NON-NLS-1$
-
- /**
- * Qualified name for the per-project non-persistent property storing the
- * {@link IncludeFinder} for this project
- */
- private final static QualifiedName INCLUDE_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID,
- "includefinder"); //$NON-NLS-1$
-
- /** Project that the include finder locates includes for */
- private final IProject mProject;
-
- /** Map from a layout resource name to a set of layouts included by the given resource */
- private Map<String, List<String>> mIncludes = null;
-
- /**
- * Reverse map of {@link #mIncludes}; points to other layouts that are including a
- * given layouts
- */
- private Map<String, List<String>> mIncludedBy = null;
-
- /** Flag set during a refresh; ignore updates when this is true */
- private static boolean sRefreshing;
-
- /** Global (cross-project) resource listener */
- private static ResourceListener sListener;
-
- /**
- * Constructs an {@link IncludeFinder} for the given project. Don't use this method;
- * use the {@link #get} factory method instead.
- *
- * @param project project to create an {@link IncludeFinder} for
- */
- private IncludeFinder(IProject project) {
- mProject = project;
- }
-
- /**
- * Returns the {@link IncludeFinder} for the given project
- *
- * @param project the project the finder is associated with
- * @return an {@link IncludeFinder} for the given project, never null
- */
- @NonNull
- public static IncludeFinder get(IProject project) {
- IncludeFinder finder = null;
- try {
- finder = (IncludeFinder) project.getSessionProperty(INCLUDE_FINDER);
- } catch (CoreException e) {
- // Not a problem; we will just create a new one
- }
-
- if (finder == null) {
- finder = new IncludeFinder(project);
- try {
- project.setSessionProperty(INCLUDE_FINDER, finder);
- } catch (CoreException e) {
- AdtPlugin.log(e, "Can't store IncludeFinder");
- }
- }
-
- return finder;
- }
-
- /**
- * Returns a list of resource names that are included by the given resource
- *
- * @param includer the resource name to return included layouts for
- * @return the layouts included by the given resource
- */
- private List<String> getIncludesFrom(String includer) {
- ensureInitialized();
-
- return mIncludes.get(includer);
- }
-
- /**
- * Gets the list of all other layouts that are including the given layout.
- *
- * @param included the file that is included
- * @return the files that are including the given file, or null or empty
- */
- @Nullable
- public List<Reference> getIncludedBy(IResource included) {
- ensureInitialized();
- String mapKey = getMapKey(included);
- List<String> result = mIncludedBy.get(mapKey);
- if (result == null) {
- String name = getResourceName(included);
- if (!name.equals(mapKey)) {
- result = mIncludedBy.get(name);
- }
- }
-
- if (result != null && result.size() > 0) {
- List<Reference> references = new ArrayList<Reference>(result.size());
- for (String s : result) {
- references.add(new Reference(mProject, s));
- }
- return references;
- } else {
- return null;
- }
- }
-
- /**
- * Returns true if the given resource is included from some other layout in the
- * project
- *
- * @param included the resource to check
- * @return true if the file is included by some other layout
- */
- public boolean isIncluded(IResource included) {
- ensureInitialized();
- String mapKey = getMapKey(included);
- List<String> result = mIncludedBy.get(mapKey);
- if (result == null) {
- String name = getResourceName(included);
- if (!name.equals(mapKey)) {
- result = mIncludedBy.get(name);
- }
- }
-
- return result != null && result.size() > 0;
- }
-
- @VisibleForTesting
- /* package */ List<String> getIncludedBy(String included) {
- ensureInitialized();
- return mIncludedBy.get(included);
- }
-
- /** Initialize the inclusion data structures, if not already done */
- private void ensureInitialized() {
- if (mIncludes == null) {
- // Initialize
- if (!readSettings()) {
- // Couldn't read settings: probably the first time this code is running
- // so there is no known data about includes.
-
- // Yes, these should be multimaps! If we start using Guava replace
- // these with multimaps.
- mIncludes = new HashMap<String, List<String>>();
- mIncludedBy = new HashMap<String, List<String>>();
-
- scanProject();
- saveSettings();
- }
- }
- }
-
- // ----- Persistence -----
-
- /**
- * Create a String serialization of the includes map. The map attempts to be compact;
- * it strips out the @layout/ prefix, and eliminates the values for empty string
- * values. The map can be restored by calling {@link #decodeMap}. The encoded String
- * will have sorted keys.
- *
- * @param map the map to be serialized
- * @return a serialization (never null) of the given map
- */
- @VisibleForTesting
- public static String encodeMap(Map<String, List<String>> map) {
- StringBuilder sb = new StringBuilder();
-
- if (map != null) {
- // Process the keys in sorted order rather than just
- // iterating over the entry set to ensure stable output
- List<String> keys = new ArrayList<String>(map.keySet());
- Collections.sort(keys);
- for (String key : keys) {
- List<String> values = map.get(key);
-
- if (sb.length() > 0) {
- sb.append(',');
- }
- sb.append(key);
- if (values.size() > 0) {
- sb.append('=').append('>');
- sb.append('{');
- boolean first = true;
- for (String value : values) {
- if (first) {
- first = false;
- } else {
- sb.append(',');
- }
- sb.append(value);
- }
- sb.append('}');
- }
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Decodes the encoding (produced by {@link #encodeMap}) back into the original map,
- * modulo any key sorting differences.
- *
- * @param encoded an encoding of a map created by {@link #encodeMap}
- * @return a map corresponding to the encoded values, never null
- */
- @VisibleForTesting
- public static Map<String, List<String>> decodeMap(String encoded) {
- HashMap<String, List<String>> map = new HashMap<String, List<String>>();
-
- if (encoded.length() > 0) {
- int i = 0;
- int end = encoded.length();
-
- while (i < end) {
-
- // Find key range
- int keyBegin = i;
- int keyEnd = i;
- while (i < end) {
- char c = encoded.charAt(i);
- if (c == ',') {
- break;
- } else if (c == '=') {
- i += 2; // Skip =>
- break;
- }
- i++;
- keyEnd = i;
- }
-
- List<String> values = new ArrayList<String>();
- // Find values
- if (i < end && encoded.charAt(i) == '{') {
- i++;
- while (i < end) {
- int valueBegin = i;
- int valueEnd = i;
- char c = 0;
- while (i < end) {
- c = encoded.charAt(i);
- if (c == ',' || c == '}') {
- valueEnd = i;
- break;
- }
- i++;
- }
- if (valueEnd > valueBegin) {
- values.add(encoded.substring(valueBegin, valueEnd));
- }
-
- if (c == '}') {
- if (i < end-1 && encoded.charAt(i+1) == ',') {
- i++;
- }
- break;
- }
- assert c == ',';
- i++;
- }
- }
-
- String key = encoded.substring(keyBegin, keyEnd);
- map.put(key, values);
- i++;
- }
- }
-
- return map;
- }
-
- /**
- * Stores the settings in the persistent project storage.
- */
- private void saveSettings() {
- // Serialize the mIncludes map into a compact String. The mIncludedBy map can be
- // inferred from it.
- String encoded = encodeMap(mIncludes);
-
- try {
- if (encoded.length() >= 2048) {
- // The maximum length of a setting key is 2KB, according to the javadoc
- // for the project class. It's unlikely that we'll
- // hit this -- even with an average layout root name of 20 characters
- // we can still store over a hundred names. But JUST IN CASE we run
- // into this, we'll clear out the key in this name which means that the
- // information will need to be recomputed in the next IDE session.
- mProject.setPersistentProperty(CONFIG_INCLUDES, null);
- } else {
- String existing = mProject.getPersistentProperty(CONFIG_INCLUDES);
- if (!encoded.equals(existing)) {
- mProject.setPersistentProperty(CONFIG_INCLUDES, encoded);
- }
- }
- } catch (CoreException e) {
- AdtPlugin.log(e, "Can't store include settings");
- }
- }
-
- /**
- * Reads previously stored settings from the persistent project storage
- *
- * @return true iff settings were restored from the project
- */
- private boolean readSettings() {
- try {
- String encoded = mProject.getPersistentProperty(CONFIG_INCLUDES);
- if (encoded != null) {
- mIncludes = decodeMap(encoded);
-
- // Set up a reverse map, pointing from included files to the files that
- // included them
- mIncludedBy = new HashMap<String, List<String>>(2 * mIncludes.size());
- for (Map.Entry<String, List<String>> entry : mIncludes.entrySet()) {
- // File containing the <include>
- String includer = entry.getKey();
- // Files being <include>'ed by the above file
- List<String> included = entry.getValue();
- setIncludedBy(includer, included);
- }
-
- return true;
- }
- } catch (CoreException e) {
- AdtPlugin.log(e, "Can't read include settings");
- }
-
- return false;
- }
-
- // ----- File scanning -----
-
- /**
- * Scan the whole project for XML layout resources that are performing includes.
- */
- private void scanProject() {
- ProjectResources resources = ResourceManager.getInstance().getProjectResources(mProject);
- if (resources != null) {
- Collection<ResourceItem> layouts = resources.getResourceItemsOfType(LAYOUT);
- for (ResourceItem layout : layouts) {
- List<ResourceFile> sources = layout.getSourceFileList();
- for (ResourceFile source : sources) {
- updateFileIncludes(source, false);
- }
- }
-
- return;
- }
- }
-
- /**
- * Scans the given {@link ResourceFile} and if it is a layout resource, updates the
- * includes in it.
- *
- * @param resourceFile the {@link ResourceFile} to be scanned for includes (doesn't
- * have to be only layout XML files; this method will filter the type)
- * @param singleUpdate true if this is a single file being updated, false otherwise
- * (e.g. during initial project scanning)
- * @return true if we updated the includes for the resource file
- */
- private boolean updateFileIncludes(ResourceFile resourceFile, boolean singleUpdate) {
- Collection<ResourceType> resourceTypes = resourceFile.getResourceTypes();
- for (ResourceType type : resourceTypes) {
- if (type == ResourceType.LAYOUT) {
- ensureInitialized();
-
- List<String> includes = Collections.emptyList();
- if (resourceFile.getFile() instanceof IFileWrapper) {
- IFile file = ((IFileWrapper) resourceFile.getFile()).getIFile();
-
- // See if we have an existing XML model for this file; if so, we can
- // just look directly at the parse tree
- boolean hadXmlModel = false;
- IStructuredModel model = null;
- try {
- IModelManager modelManager = StructuredModelManager.getModelManager();
- model = modelManager.getExistingModelForRead(file);
- if (model instanceof IDOMModel) {
- IDOMModel domModel = (IDOMModel) model;
- Document document = domModel.getDocument();
- includes = findIncludesInDocument(document);
- hadXmlModel = true;
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- // If no XML model we have to read the XML contents and (possibly) parse it.
- // The actual file may not exist anymore (e.g. when deleting a layout file
- // or when the workspace is out of sync.)
- if (!hadXmlModel) {
- String xml = AdtPlugin.readFile(file);
- if (xml != null) {
- includes = findIncludes(xml);
- }
- }
- } else {
- String xml = AdtPlugin.readFile(resourceFile);
- if (xml != null) {
- includes = findIncludes(xml);
- }
- }
-
- String key = getMapKey(resourceFile);
- if (includes.equals(getIncludesFrom(key))) {
- // Common case -- so avoid doing settings flush etc
- return false;
- }
-
- boolean detectCycles = singleUpdate;
- setIncluded(key, includes, detectCycles);
-
- if (singleUpdate) {
- saveSettings();
- }
-
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Finds the list of includes in the given XML content. It attempts quickly return
- * empty if the file does not include any include tags; it does this by only parsing
- * if it detects the string &lt;include in the file.
- */
- @VisibleForTesting
- @NonNull
- static List<String> findIncludes(@NonNull String xml) {
- int index = xml.indexOf(ATTR_LAYOUT);
- if (index != -1) {
- return findIncludesInXml(xml);
- }
-
- return Collections.emptyList();
- }
-
- /**
- * Parses the given XML content and extracts all the included URLs and returns them
- *
- * @param xml layout XML content to be parsed for includes
- * @return a list of included urls, or null
- */
- @VisibleForTesting
- @NonNull
- static List<String> findIncludesInXml(@NonNull String xml) {
- Document document = DomUtilities.parseDocument(xml, false /*logParserErrors*/);
- if (document != null) {
- return findIncludesInDocument(document);
- }
-
- return Collections.emptyList();
- }
-
- /** Searches the given DOM document and returns the list of includes, if any */
- @NonNull
- private static List<String> findIncludesInDocument(@NonNull Document document) {
- List<String> includes = findIncludesInDocument(document, null);
- if (includes == null) {
- includes = Collections.emptyList();
- }
- return includes;
- }
-
- @Nullable
- private static List<String> findIncludesInDocument(@NonNull Node node,
- @Nullable List<String> urls) {
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- String tag = node.getNodeName();
- boolean isInclude = tag.equals(VIEW_INCLUDE);
- boolean isFragment = tag.equals(VIEW_FRAGMENT);
- if (isInclude || isFragment) {
- Element element = (Element) node;
- String url;
- if (isInclude) {
- url = element.getAttribute(ATTR_LAYOUT);
- } else {
- url = element.getAttributeNS(TOOLS_URI, ATTR_LAYOUT);
- }
- if (url.length() > 0) {
- String resourceName = urlToLocalResource(url);
- if (resourceName != null) {
- if (urls == null) {
- urls = new ArrayList<String>();
- }
- urls.add(resourceName);
- }
- }
-
- }
- }
-
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- urls = findIncludesInDocument(children.item(i), urls);
- }
-
- return urls;
- }
-
-
- /**
- * Returns the layout URL to a local resource name (provided the URL is a local
- * resource, not something in @android etc.) Returns null otherwise.
- */
- private static String urlToLocalResource(String url) {
- if (!url.startsWith("@")) { //$NON-NLS-1$
- return null;
- }
- int typeEnd = url.indexOf('/', 1);
- if (typeEnd == -1) {
- return null;
- }
- int nameBegin = typeEnd + 1;
- int typeBegin = 1;
- int colon = url.lastIndexOf(':', typeEnd);
- if (colon != -1) {
- String packageName = url.substring(typeBegin, colon);
- if ("android".equals(packageName)) { //$NON-NLS-1$
- // Don't want to point to non-local resources
- return null;
- }
-
- typeBegin = colon + 1;
- assert "layout".equals(url.substring(typeBegin, typeEnd)); //$NON-NLS-1$
- }
-
- return url.substring(nameBegin);
- }
-
- /**
- * Record the list of included layouts from the given layout
- *
- * @param includer the layout including other layouts
- * @param included the layouts that were included by the including layout
- * @param detectCycles if true, check for cycles and report them as project errors
- */
- @VisibleForTesting
- /* package */ void setIncluded(String includer, List<String> included, boolean detectCycles) {
- // Remove previously linked inverse mappings
- List<String> oldIncludes = mIncludes.get(includer);
- if (oldIncludes != null && oldIncludes.size() > 0) {
- for (String includee : oldIncludes) {
- List<String> includers = mIncludedBy.get(includee);
- if (includers != null) {
- includers.remove(includer);
- }
- }
- }
-
- mIncludes.put(includer, included);
- // Reverse mapping: for included items, point back to including file
- setIncludedBy(includer, included);
-
- if (detectCycles) {
- detectCycles(includer);
- }
- }
-
- /** Record the list of included layouts from the given layout */
- private void setIncludedBy(String includer, List<String> included) {
- for (String target : included) {
- List<String> list = mIncludedBy.get(target);
- if (list == null) {
- list = new ArrayList<String>(2); // We don't expect many includes
- mIncludedBy.put(target, list);
- }
- if (!list.contains(includer)) {
- list.add(includer);
- }
- }
- }
-
- /** Start listening on project resources */
- public static void start() {
- assert sListener == null;
- sListener = new ResourceListener();
- ResourceManager.getInstance().addListener(sListener);
- }
-
- /** Stop listening on project resources */
- public static void stop() {
- assert sListener != null;
- ResourceManager.getInstance().addListener(sListener);
- }
-
- private static String getMapKey(ResourceFile resourceFile) {
- IAbstractFile file = resourceFile.getFile();
- String name = file.getName();
- String folderName = file.getParentFolder().getName();
- return getMapKey(folderName, name);
- }
-
- private static String getMapKey(IResource resourceFile) {
- String folderName = resourceFile.getParent().getName();
- String name = resourceFile.getName();
- return getMapKey(folderName, name);
- }
-
- private static String getResourceName(IResource resourceFile) {
- String name = resourceFile.getName();
- int baseEnd = name.length() - EXT_XML.length() - 1; // -1: the dot
- if (baseEnd > 0) {
- name = name.substring(0, baseEnd);
- }
-
- return name;
- }
-
- private static String getMapKey(String folderName, String name) {
- int baseEnd = name.length() - EXT_XML.length() - 1; // -1: the dot
- if (baseEnd > 0) {
- name = name.substring(0, baseEnd);
- }
-
- // Create a map key for the given resource file
- // This will map
- // /res/layout/foo.xml => "foo"
- // /res/layout-land/foo.xml => "-land/foo"
-
- if (FD_RES_LAYOUT.equals(folderName)) {
- // Normal case -- keep just the basename
- return name;
- } else {
- // Store the relative path from res/ on down, so
- // /res/layout-land/foo.xml becomes "layout-land/foo"
- //if (folderName.startsWith(FD_LAYOUT)) {
- // folderName = folderName.substring(FD_LAYOUT.length());
- //}
-
- return folderName + WS_SEP + name;
- }
- }
-
- /** Listener of resource file saves, used to update layout inclusion data structures */
- private static class ResourceListener implements IResourceListener {
- @Override
- public void fileChanged(IProject project, ResourceFile file, int eventType) {
- if (sRefreshing) {
- return;
- }
-
- if ((eventType & (CHANGED | ADDED | REMOVED | CONTENT)) == 0) {
- return;
- }
-
- IncludeFinder finder = get(project);
- if (finder != null) {
- if (finder.updateFileIncludes(file, true)) {
- finder.saveSettings();
- }
- }
- }
-
- @Override
- public void folderChanged(IProject project, ResourceFolder folder, int eventType) {
- // We only care about layout resource files
- }
- }
-
- // ----- Cycle detection -----
-
- private void detectCycles(String from) {
- // Perform DFS on the include graph and look for a cycle; if we find one, produce
- // a chain of includes on the way back to show to the user
- if (mIncludes.size() > 0) {
- Set<String> visiting = new HashSet<String>(mIncludes.size());
- String chain = dfs(from, visiting);
- if (chain != null) {
- addError(from, chain);
- } else {
- // Is there an existing error for us to clean up?
- removeErrors(from);
- }
- }
- }
-
- /** Format to chain include cycles in: a=>b=>c=>d etc */
- private final String CHAIN_FORMAT = "%1$s=>%2$s"; //$NON-NLS-1$
-
- private String dfs(String from, Set<String> visiting) {
- visiting.add(from);
-
- List<String> includes = mIncludes.get(from);
- if (includes != null && includes.size() > 0) {
- for (String include : includes) {
- if (visiting.contains(include)) {
- return String.format(CHAIN_FORMAT, from, include);
- }
- String chain = dfs(include, visiting);
- if (chain != null) {
- return String.format(CHAIN_FORMAT, from, chain);
- }
- }
- }
-
- visiting.remove(from);
-
- return null;
- }
-
- private void removeErrors(String from) {
- final IResource resource = findResource(from);
- if (resource != null) {
- try {
- final String markerId = IMarker.PROBLEM;
-
- IMarker[] markers = resource.findMarkers(markerId, true, IResource.DEPTH_ZERO);
-
- for (final IMarker marker : markers) {
- String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null);
- if (tmpMsg == null || tmpMsg.startsWith(MESSAGE)) {
- // Remove
- runLater(new Runnable() {
- @Override
- public void run() {
- try {
- sRefreshing = true;
- marker.delete();
- } catch (CoreException e) {
- AdtPlugin.log(e, "Can't delete problem marker");
- } finally {
- sRefreshing = false;
- }
- }
- });
- }
- }
- } catch (CoreException e) {
- // if we couldn't get the markers, then we just mark the file again
- // (since markerAlreadyExists is initialized to false, we do nothing)
- }
- }
- }
-
- /** Error message for cycles */
- private static final String MESSAGE = "Found cyclical <include> chain";
-
- private void addError(String from, String chain) {
- final IResource resource = findResource(from);
- if (resource != null) {
- final String markerId = IMarker.PROBLEM;
- final String message = String.format("%1$s: %2$s", MESSAGE, chain);
- final int lineNumber = 1;
- final int severity = IMarker.SEVERITY_ERROR;
-
- // check if there's a similar marker already, since aapt is launched twice
- boolean markerAlreadyExists = false;
- try {
- IMarker[] markers = resource.findMarkers(markerId, true, IResource.DEPTH_ZERO);
-
- for (IMarker marker : markers) {
- int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
- if (tmpLine != lineNumber) {
- break;
- }
-
- int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1);
- if (tmpSeverity != severity) {
- break;
- }
-
- String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null);
- if (tmpMsg == null || tmpMsg.equals(message) == false) {
- break;
- }
-
- // if we're here, all the marker attributes are equals, we found it
- // and exit
- markerAlreadyExists = true;
- break;
- }
-
- } catch (CoreException e) {
- // if we couldn't get the markers, then we just mark the file again
- // (since markerAlreadyExists is initialized to false, we do nothing)
- }
-
- if (!markerAlreadyExists) {
- runLater(new Runnable() {
- @Override
- public void run() {
- try {
- sRefreshing = true;
-
- // Adding a resource will force a refresh on the file;
- // ignore these updates
- BaseProjectHelper.markResource(resource, markerId, message, lineNumber,
- severity);
- } finally {
- sRefreshing = false;
- }
- }
- });
- }
- }
- }
-
- // FIXME: Find more standard Eclipse way to do this.
- // We need to run marker registration/deletion "later", because when the include
- // scanning is running it's in the middle of resource notification, so the IDE
- // throws an exception
- private static void runLater(Runnable runnable) {
- Display display = Display.findDisplay(Thread.currentThread());
- if (display != null) {
- display.asyncExec(runnable);
- } else {
- AdtPlugin.log(IStatus.WARNING, "Could not find display");
- }
- }
-
- /**
- * Finds the project resource for the given layout path
- *
- * @param from the resource name
- * @return the {@link IResource}, or null if not found
- */
- private IResource findResource(String from) {
- final IResource resource = mProject.findMember(WS_LAYOUTS + WS_SEP + from + '.' + EXT_XML);
- return resource;
- }
-
- /**
- * Creates a blank, project-less {@link IncludeFinder} <b>for use by unit tests
- * only</b>
- */
- @VisibleForTesting
- /* package */ static IncludeFinder create() {
- IncludeFinder finder = new IncludeFinder(null);
- finder.mIncludes = new HashMap<String, List<String>>();
- finder.mIncludedBy = new HashMap<String, List<String>>();
- return finder;
- }
-
- /** A reference to a particular file in the project */
- public static class Reference {
- /** The unique id referencing the file, such as (for res/layout-land/main.xml)
- * "layout-land/main") */
- private final String mId;
-
- /** The project containing the file */
- private final IProject mProject;
-
- /** The resource name of the file, such as (for res/layout/main.xml) "main" */
- private String mName;
-
- /** Creates a new include reference */
- private Reference(IProject project, String id) {
- super();
- mProject = project;
- mId = id;
- }
-
- /**
- * Returns the id identifying the given file within the project
- *
- * @return the id identifying the given file within the project
- */
- public String getId() {
- return mId;
- }
-
- /**
- * Returns the {@link IFile} in the project for the given file. May return null if
- * there is an error in locating the file or if the file no longer exists.
- *
- * @return the project file, or null
- */
- public IFile getFile() {
- String reference = mId;
- if (!reference.contains(WS_SEP)) {
- reference = FD_RES_LAYOUT + WS_SEP + reference;
- }
-
- String projectPath = FD_RESOURCES + WS_SEP + reference + '.' + EXT_XML;
- IResource member = mProject.findMember(projectPath);
- if (member instanceof IFile) {
- return (IFile) member;
- }
-
- return null;
- }
-
- /**
- * Returns a description of this reference, suitable to be shown to the user
- *
- * @return a display name for the reference
- */
- public String getDisplayName() {
- // The ID is deliberately kept in a pretty user-readable format but we could
- // consider prepending layout/ on ids that don't have it (to make the display
- // more uniform) or ripping out all layout[-constraint] prefixes out and
- // instead prepending @ etc.
- return mId;
- }
-
- /**
- * Returns the name of the reference, suitable for resource lookup. For example,
- * for "res/layout/main.xml", as well as for "res/layout-land/main.xml", this
- * would be "main".
- *
- * @return the resource name of the reference
- */
- public String getName() {
- if (mName == null) {
- mName = mId;
- int index = mName.lastIndexOf(WS_SEP);
- if (index != -1) {
- mName = mName.substring(index + 1);
- }
- }
-
- return mName;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mId == null) ? 0 : mId.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Reference other = (Reference) obj;
- if (mId == null) {
- if (other.mId != null)
- return false;
- } else if (!mId.equals(other.mId))
- return false;
- return true;
- }
-
- @Override
- public String toString() {
- return "Reference [getId()=" + getId() //$NON-NLS-1$
- + ", getDisplayName()=" + getDisplayName() //$NON-NLS-1$
- + ", getName()=" + getName() //$NON-NLS-1$
- + ", getFile()=" + getFile() + "]"; //$NON-NLS-1$
- }
-
- /**
- * Creates a reference to the given file
- *
- * @param file the file to create a reference for
- * @return a reference to the given file
- */
- public static Reference create(IFile file) {
- return new Reference(file.getProject(), getMapKey(file));
- }
-
- /**
- * Returns the resource name of this layout, such as {@code @layout/foo}.
- *
- * @return the resource name
- */
- public String getResourceName() {
- return '@' + FD_RES_LAYOUT + '/' + getName();
- }
- }
-
- /**
- * Returns a collection of layouts (expressed as resource names, such as
- * {@code @layout/foo} which would be invalid includes in the given layout
- * (because it would introduce a cycle)
- *
- * @param layout the layout file to check for cyclic dependencies from
- * @return a collection of layout resources which cannot be included from
- * the given layout, never null
- */
- public Collection<String> getInvalidIncludes(IFile layout) {
- IProject project = layout.getProject();
- Reference self = Reference.create(layout);
-
- // Add anyone who transitively can reach this file via includes.
- LinkedList<Reference> queue = new LinkedList<Reference>();
- List<Reference> invalid = new ArrayList<Reference>();
- queue.add(self);
- invalid.add(self);
- Set<String> seen = new HashSet<String>();
- seen.add(self.getId());
- while (!queue.isEmpty()) {
- Reference reference = queue.removeFirst();
- String refId = reference.getId();
-
- // Look up both configuration specific includes as well as includes in the
- // base versions
- List<String> included = getIncludedBy(refId);
- if (refId.indexOf('/') != -1) {
- List<String> baseIncluded = getIncludedBy(reference.getName());
- if (included == null) {
- included = baseIncluded;
- } else if (baseIncluded != null) {
- included = new ArrayList<String>(included);
- included.addAll(baseIncluded);
- }
- }
-
- if (included != null && included.size() > 0) {
- for (String id : included) {
- if (!seen.contains(id)) {
- seen.add(id);
- Reference ref = new Reference(project, id);
- invalid.add(ref);
- queue.addLast(ref);
- }
- }
- }
- }
-
- List<String> result = new ArrayList<String>();
- for (Reference reference : invalid) {
- result.add(reference.getResourceName());
- }
-
- return result;
- }
-}