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