Home | History | Annotate | Download | only in manager
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.resources.manager;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.NonNull;
     21 import com.android.annotations.Nullable;
     22 import com.android.ide.common.resources.IntArrayWrapper;
     23 import com.android.ide.common.xml.ManifestData;
     24 import com.android.ide.eclipse.adt.AdtConstants;
     25 import com.android.ide.eclipse.adt.AdtPlugin;
     26 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     27 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     28 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
     29 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
     30 import com.android.resources.ResourceType;
     31 import com.android.util.Pair;
     32 
     33 import org.eclipse.core.resources.IFile;
     34 import org.eclipse.core.resources.IMarkerDelta;
     35 import org.eclipse.core.resources.IProject;
     36 import org.eclipse.core.resources.IResource;
     37 import org.eclipse.core.resources.IResourceDelta;
     38 import org.eclipse.core.runtime.CoreException;
     39 import org.eclipse.core.runtime.IPath;
     40 import org.eclipse.core.runtime.IStatus;
     41 
     42 import java.io.File;
     43 import java.lang.reflect.Field;
     44 import java.lang.reflect.Modifier;
     45 import java.util.EnumMap;
     46 import java.util.HashMap;
     47 import java.util.Map;
     48 import java.util.regex.Pattern;
     49 
     50 /**
     51  * A monitor for the compiled resources. This only monitors changes in the resources of type
     52  *  {@link ResourceType#ID}.
     53  */
     54 public final class CompiledResourcesMonitor implements IFileListener, IProjectListener {
     55 
     56     private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor();
     57 
     58     /**
     59      * Sets up the monitoring system.
     60      * @param monitor The main Resource Monitor.
     61      */
     62     public static void setupMonitor(GlobalProjectMonitor monitor) {
     63         monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED);
     64         monitor.addProjectListener(sThis);
     65     }
     66 
     67     /**
     68      * private constructor to prevent construction.
     69      */
     70     private CompiledResourcesMonitor() {
     71     }
     72 
     73 
     74     /* (non-Javadoc)
     75      * Sent when a file changed : if the file is the R class, then it is parsed again to update
     76      * the internal data.
     77      *
     78      * @param file The file that changed.
     79      * @param markerDeltas The marker deltas for the file.
     80      * @param kind The change kind. This is equivalent to
     81      * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
     82      *
     83      * @see IFileListener#fileChanged
     84      */
     85     @Override
     86     public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
     87             int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
     88         if (!isAndroidProject || flags == IResourceDelta.MARKERS) {
     89             // Not Android or only the markers changed: not relevant
     90             return;
     91         }
     92 
     93         IProject project = file.getProject();
     94 
     95         if (file.getName().equals(SdkConstants.FN_COMPILED_RESOURCE_CLASS)) {
     96             // create the classname
     97             String className = getRClassName(project);
     98             if (className == null) {
     99                 // We need to abort.
    100                 AdtPlugin.log(IStatus.ERROR,
    101                         "fileChanged: failed to find manifest package for project %1$s", //$NON-NLS-1$
    102                         project.getName());
    103                 return;
    104             }
    105             // path will begin with /projectName/bin/classes so we'll ignore that
    106             IPath relativeClassPath = file.getFullPath().removeFirstSegments(3);
    107             if (packagePathMatches(relativeClassPath.toString(), className)) {
    108                 loadAndParseRClass(project, className);
    109             }
    110         }
    111     }
    112 
    113     /**
    114      * Check to see if the package section of the given path matches the packageName.
    115      * For example, /project/bin/classes/com/foo/app/R.class should match com.foo.app.R
    116      * @param path the pathname of the file to look at
    117      * @param packageName the package qualified name of the class
    118      * @return true if the package section of the path matches the package qualified name
    119      */
    120     private boolean packagePathMatches(String path, String packageName) {
    121         // First strip the ".class" off the end of the path
    122         String pathWithoutExtension = path.substring(0, path.indexOf(SdkConstants.DOT_CLASS));
    123 
    124         // then split the components of each path by their separators
    125         String [] pathArray = pathWithoutExtension.split(Pattern.quote(File.separator));
    126         String [] packageArray = packageName.split(AdtConstants.RE_DOT);
    127 
    128 
    129         int pathIndex = 0;
    130         int packageIndex = 0;
    131 
    132         while (pathIndex < pathArray.length && packageIndex < packageArray.length) {
    133             if (pathArray[pathIndex].equals(packageArray[packageIndex]) == false) {
    134                 return false;
    135             }
    136             pathIndex++;
    137             packageIndex++;
    138         }
    139         // We may have matched all the way up to this point, but we're not sure it's a match
    140         // unless BOTH paths done
    141         return (pathIndex == pathArray.length && packageIndex == packageArray.length);
    142     }
    143 
    144     /**
    145      * Processes project close event.
    146      */
    147     @Override
    148     public void projectClosed(IProject project) {
    149         // the ProjectResources object will be removed by the ResourceManager.
    150     }
    151 
    152     /**
    153      * Processes project delete event.
    154      */
    155     @Override
    156     public void projectDeleted(IProject project) {
    157         // the ProjectResources object will be removed by the ResourceManager.
    158     }
    159 
    160     /**
    161      * Processes project open event.
    162      */
    163     @Override
    164     public void projectOpened(IProject project) {
    165         // when the project is opened, we get an ADDED event for each file, so we don't
    166         // need to do anything here.
    167     }
    168 
    169     @Override
    170     public void projectRenamed(IProject project, IPath from) {
    171         // renamed projects also trigger delete/open event,
    172         // so nothing to be done here.
    173     }
    174 
    175     /**
    176      * Processes existing project at init time.
    177      */
    178     @Override
    179     public void projectOpenedWithWorkspace(IProject project) {
    180         try {
    181             // check this is an android project
    182             if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
    183                 String className = getRClassName(project);
    184                 // Find the classname
    185                 if (className == null) {
    186                     // We need to abort.
    187                     AdtPlugin.log(IStatus.ERROR,
    188                             "projectOpenedWithWorkspace: failed to find manifest package for project %1$s", //$NON-NLS-1$
    189                             project.getName());
    190                     return;
    191                 }
    192                 loadAndParseRClass(project, className);
    193             }
    194         } catch (CoreException e) {
    195             // pass
    196         }
    197     }
    198 
    199     @Override
    200     public void allProjectsOpenedWithWorkspace() {
    201         // nothing to do.
    202     }
    203 
    204 
    205     private void loadAndParseRClass(IProject project, String className) {
    206         try {
    207             // first check there's a ProjectResources to store the content
    208             ProjectResources projectResources = ResourceManager.getInstance().getProjectResources(
    209                     project);
    210 
    211             if (projectResources != null) {
    212                 // create a temporary class loader to load the class
    213                 ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */,
    214                         project);
    215 
    216                 try {
    217                     Class<?> clazz = loader.loadClass(className);
    218 
    219                     if (clazz != null) {
    220                         // create the maps to store the result of the parsing
    221                         Map<ResourceType, Map<String, Integer>> resourceValueMap =
    222                             new EnumMap<ResourceType, Map<String, Integer>>(ResourceType.class);
    223                         Map<Integer, Pair<ResourceType, String>> genericValueToNameMap =
    224                             new HashMap<Integer, Pair<ResourceType, String>>();
    225                         Map<IntArrayWrapper, String> styleableValueToNameMap =
    226                             new HashMap<IntArrayWrapper, String>();
    227 
    228                         // parse the class
    229                         if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap,
    230                                 resourceValueMap)) {
    231                             // now we associate the maps to the project.
    232                             projectResources.setCompiledResources(genericValueToNameMap,
    233                                     styleableValueToNameMap, resourceValueMap);
    234                         }
    235                     }
    236                 } catch (Error e) {
    237                     // Log this error with the class name we're trying to load and abort.
    238                     AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$
    239                 }
    240             }
    241         } catch (ClassNotFoundException e) {
    242             // pass
    243         }
    244     }
    245 
    246     /**
    247      * Parses a R class, and fills maps.
    248      * @param rClass the class to parse
    249      * @param genericValueToNameMap
    250      * @param styleableValueToNameMap
    251      * @param resourceValueMap
    252      * @return True if we managed to parse the R class.
    253      */
    254     private boolean parseClass(Class<?> rClass,
    255             Map<Integer, Pair<ResourceType, String>> genericValueToNameMap,
    256             Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType,
    257             Map<String, Integer>> resourceValueMap) {
    258         try {
    259             for (Class<?> inner : rClass.getDeclaredClasses()) {
    260                 String resTypeName = inner.getSimpleName();
    261                 ResourceType resType = ResourceType.getEnum(resTypeName);
    262 
    263                 if (resType != null) {
    264                     Map<String, Integer> fullMap = new HashMap<String, Integer>();
    265                     resourceValueMap.put(resType, fullMap);
    266 
    267                     for (Field f : inner.getDeclaredFields()) {
    268                         // only process static final fields.
    269                         int modifiers = f.getModifiers();
    270                         if (Modifier.isStatic(modifiers)) {
    271                             Class<?> type = f.getType();
    272                             if (type.isArray() && type.getComponentType() == int.class) {
    273                                 // if the object is an int[] we put it in the styleable map
    274                                 styleableValueToNameMap.put(
    275                                         new IntArrayWrapper((int[]) f.get(null)),
    276                                         f.getName());
    277                             } else if (type == int.class) {
    278                                 Integer value = (Integer) f.get(null);
    279                                 genericValueToNameMap.put(value, Pair.of(resType, f.getName()));
    280                                 fullMap.put(f.getName(), value);
    281                             } else {
    282                                 assert false;
    283                             }
    284                         }
    285                     }
    286                 }
    287             }
    288 
    289             return true;
    290         } catch (IllegalArgumentException e) {
    291         } catch (IllegalAccessException e) {
    292         }
    293         return false;
    294     }
    295 
    296     /**
    297      * Returns the class name of the R class, based on the project's manifest's package.
    298      *
    299      * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest.
    300      */
    301     private String getRClassName(IProject project) {
    302         IFile manifestFile = ProjectHelper.getManifest(project);
    303         if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) {
    304             ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
    305             if (data != null) {
    306                 String javaPackage = data.getPackage();
    307                 return javaPackage + ".R"; //$NON-NLS-1$
    308             }
    309         }
    310         return null;
    311     }
    312 
    313 }
    314