Home | History | Annotate | Download | only in sdk
      1 /*
      2  * Copyright (C) 2008 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.sdk;
     18 
     19 import com.android.sdklib.SdkConstants;
     20 
     21 import org.eclipse.core.runtime.IProgressMonitor;
     22 
     23 import java.io.BufferedReader;
     24 import java.io.FileNotFoundException;
     25 import java.io.FileReader;
     26 import java.io.IOException;
     27 import java.util.ArrayList;
     28 import java.util.Collection;
     29 import java.util.HashMap;
     30 import java.util.Map;
     31 import java.util.TreeMap;
     32 
     33 import javax.management.InvalidAttributeValueException;
     34 
     35 /**
     36  * Parser for the text file containing the list of widgets, layouts and layout params.
     37  * <p/>
     38  * The file is a straight text file containing one class per line.<br>
     39  * Each line is in the following format<br>
     40  * <code>[code][class name] [super class name] [super class name]...</code>
     41  * where code is a single letter (W for widget, L for layout, P for layout params), and class names
     42  * are the fully qualified name of the classes.
     43  */
     44 public final class WidgetClassLoader implements IAndroidClassLoader {
     45 
     46     /**
     47      * Basic class containing the class descriptions found in the text file.
     48      */
     49     private final static class ClassDescriptor implements IClassDescriptor {
     50 
     51         private String mFqcn;
     52         private String mSimpleName;
     53         private ClassDescriptor mSuperClass;
     54         private ClassDescriptor mEnclosingClass;
     55         private final ArrayList<IClassDescriptor> mDeclaredClasses =
     56                 new ArrayList<IClassDescriptor>();
     57         private boolean mIsInstantiable = false;
     58 
     59         ClassDescriptor(String fqcn) {
     60             mFqcn = fqcn;
     61             mSimpleName = getSimpleName(fqcn);
     62         }
     63 
     64         public String getFullClassName() {
     65             return mFqcn;
     66         }
     67 
     68         public String getSimpleName() {
     69             return mSimpleName;
     70         }
     71 
     72         public IClassDescriptor[] getDeclaredClasses() {
     73             return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]);
     74         }
     75 
     76         private void addDeclaredClass(ClassDescriptor declaredClass) {
     77             mDeclaredClasses.add(declaredClass);
     78         }
     79 
     80         public IClassDescriptor getEnclosingClass() {
     81             return mEnclosingClass;
     82         }
     83 
     84         void setEnclosingClass(ClassDescriptor enclosingClass) {
     85             // set the enclosing class.
     86             mEnclosingClass = enclosingClass;
     87 
     88             // add this to the list of declared class in the enclosing class.
     89             mEnclosingClass.addDeclaredClass(this);
     90 
     91             // finally change the name of declared class to make sure it uses the
     92             // convention: package.enclosing$declared instead of package.enclosing.declared
     93             mFqcn = enclosingClass.mFqcn + "$" + mFqcn.substring(enclosingClass.mFqcn.length() + 1);
     94         }
     95 
     96         public IClassDescriptor getSuperclass() {
     97             return mSuperClass;
     98         }
     99 
    100         void setSuperClass(ClassDescriptor superClass) {
    101             mSuperClass = superClass;
    102         }
    103 
    104         @Override
    105         public boolean equals(Object clazz) {
    106             if (clazz instanceof ClassDescriptor) {
    107                 return mFqcn.equals(((ClassDescriptor)clazz).mFqcn);
    108             }
    109             return super.equals(clazz);
    110         }
    111 
    112         @Override
    113         public int hashCode() {
    114             return mFqcn.hashCode();
    115         }
    116 
    117         public boolean isInstantiable() {
    118             return mIsInstantiable;
    119         }
    120 
    121         void setInstantiable(boolean state) {
    122             mIsInstantiable = state;
    123         }
    124 
    125         private String getSimpleName(String fqcn) {
    126             String[] segments = fqcn.split("\\.");
    127             return segments[segments.length-1];
    128         }
    129     }
    130 
    131     private BufferedReader mReader;
    132 
    133     /** Output map of FQCN => descriptor on all classes */
    134     private final Map<String, ClassDescriptor> mMap = new TreeMap<String, ClassDescriptor>();
    135     /** Output map of FQCN => descriptor on View classes */
    136     private final Map<String, ClassDescriptor> mWidgetMap = new TreeMap<String, ClassDescriptor>();
    137     /** Output map of FQCN => descriptor on ViewGroup classes */
    138     private final Map<String, ClassDescriptor> mLayoutMap = new TreeMap<String, ClassDescriptor>();
    139     /** Output map of FQCN => descriptor on LayoutParams classes */
    140     private final Map<String, ClassDescriptor> mLayoutParamsMap =
    141         new HashMap<String, ClassDescriptor>();
    142     /** File path of the source text file */
    143     private String mOsFilePath;
    144 
    145     /**
    146      * Creates a loader with a given file path.
    147      * @param osFilePath the OS path of the file to load.
    148      * @throws FileNotFoundException if the file is not found.
    149      */
    150     WidgetClassLoader(String osFilePath) throws FileNotFoundException {
    151         mOsFilePath = osFilePath;
    152         mReader = new BufferedReader(new FileReader(osFilePath));
    153     }
    154 
    155     public String getSource() {
    156         return mOsFilePath;
    157     }
    158 
    159     /**
    160      * Parses the text file and return true if the file was successfully parsed.
    161      * @param monitor
    162      */
    163     boolean parseWidgetList(IProgressMonitor monitor) {
    164         try {
    165             String line;
    166             while ((line = mReader.readLine()) != null) {
    167                 if (line.length() > 0) {
    168                     char prefix = line.charAt(0);
    169                     String[] classes = null;
    170                     ClassDescriptor clazz = null;
    171                     switch (prefix) {
    172                         case 'W':
    173                             classes = line.substring(1).split(" ");
    174                             clazz = processClass(classes, 0, null /* map */);
    175                             if (clazz != null) {
    176                                 clazz.setInstantiable(true);
    177                                 mWidgetMap.put(classes[0], clazz);
    178                             }
    179                             break;
    180                         case 'L':
    181                             classes = line.substring(1).split(" ");
    182                             clazz = processClass(classes, 0, null /* map */);
    183                             if (clazz != null) {
    184                                 clazz.setInstantiable(true);
    185                                 mLayoutMap.put(classes[0], clazz);
    186                             }
    187                             break;
    188                         case 'P':
    189                             classes = line.substring(1).split(" ");
    190                             clazz = processClass(classes, 0, mLayoutParamsMap);
    191                             if (clazz != null) {
    192                                 clazz.setInstantiable(true);
    193                             }
    194                             break;
    195                         case '#':
    196                             // comment, do nothing
    197                             break;
    198                         default:
    199                                 throw new IllegalArgumentException();
    200                     }
    201                 }
    202             }
    203 
    204             // reconciliate the layout and their layout params
    205             postProcess();
    206 
    207             return true;
    208         } catch (IOException e) {
    209         } finally {
    210             try {
    211                 mReader.close();
    212             } catch (IOException e) {
    213             }
    214         }
    215 
    216         return false;
    217     }
    218 
    219     /**
    220      * Parses a View class and adds a ViewClassInfo for it in mWidgetMap.
    221      * It calls itself recursively to handle super classes which are also Views.
    222      * @param classes the inheritance list of the class to process.
    223      * @param index the index of the class to process in the <code>classes</code> array.
    224      * @param map an optional map in which to put every {@link ClassDescriptor} created.
    225      */
    226     private ClassDescriptor processClass(String[] classes, int index,
    227             Map<String, ClassDescriptor> map) {
    228         if (index >= classes.length) {
    229             return null;
    230         }
    231 
    232         String fqcn = classes[index];
    233 
    234         if ("java.lang.Object".equals(fqcn)) { //$NON-NLS-1$
    235             return null;
    236         }
    237 
    238         // check if the ViewInfoClass has not yet been created.
    239         if (mMap.containsKey(fqcn)) {
    240             return mMap.get(fqcn);
    241         }
    242 
    243         // create the custom class.
    244         ClassDescriptor clazz = new ClassDescriptor(fqcn);
    245         mMap.put(fqcn, clazz);
    246         if (map != null) {
    247             map.put(fqcn, clazz);
    248         }
    249 
    250         // get the super class
    251         ClassDescriptor superClass = processClass(classes, index+1, map);
    252         if (superClass != null) {
    253             clazz.setSuperClass(superClass);
    254         }
    255 
    256         return clazz;
    257     }
    258 
    259     /**
    260      * Goes through the layout params and look for the enclosed class. If the layout params
    261      * has no known enclosed type it is dropped.
    262      */
    263     private void postProcess() {
    264         Collection<ClassDescriptor> params = mLayoutParamsMap.values();
    265 
    266         for (ClassDescriptor param : params) {
    267             String fqcn = param.getFullClassName();
    268 
    269             // get the enclosed name.
    270             String enclosed = getEnclosedName(fqcn);
    271 
    272             // look for a match in the layouts. We don't use the layout map as it only contains the
    273             // end classes, but in this case we also need to process the layout params for the base
    274             // layout classes.
    275             ClassDescriptor enclosingType = mMap.get(enclosed);
    276             if (enclosingType != null) {
    277                 param.setEnclosingClass(enclosingType);
    278 
    279                 // remove the class from the map, and put it back with the fixed name
    280                 mMap.remove(fqcn);
    281                 mMap.put(param.getFullClassName(), param);
    282             }
    283         }
    284     }
    285 
    286     private String getEnclosedName(String fqcn) {
    287         int index = fqcn.lastIndexOf('.');
    288         return fqcn.substring(0, index);
    289     }
    290 
    291     /**
    292      * Finds and loads all classes that derive from a given set of super classes.
    293      *
    294      * @param rootPackage Root package of classes to find. Use an empty string to find everyting.
    295      * @param superClasses The super classes of all the classes to find.
    296      * @return An hash map which keys are the super classes looked for and which values are
    297      *         ArrayList of the classes found. The array lists are always created for all the
    298      *         valid keys, they are simply empty if no deriving class is found for a given
    299      *         super class.
    300      * @throws IOException
    301      * @throws InvalidAttributeValueException
    302      * @throws ClassFormatError
    303      */
    304     public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage,
    305             String[] superClasses) throws IOException, InvalidAttributeValueException,
    306             ClassFormatError {
    307         HashMap<String, ArrayList<IClassDescriptor>> map =
    308                 new HashMap<String, ArrayList<IClassDescriptor>>();
    309 
    310         ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>();
    311         list.addAll(mWidgetMap.values());
    312         map.put(SdkConstants.CLASS_VIEW, list);
    313 
    314         list = new ArrayList<IClassDescriptor>();
    315         list.addAll(mLayoutMap.values());
    316         map.put(SdkConstants.CLASS_VIEWGROUP, list);
    317 
    318         list = new ArrayList<IClassDescriptor>();
    319         list.addAll(mLayoutParamsMap.values());
    320         map.put(SdkConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
    321 
    322         return map;
    323     }
    324 
    325     /**
    326      * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
    327      * @param className the fully-qualified name of the class to return.
    328      * @throws ClassNotFoundException
    329      */
    330     public IClassDescriptor getClass(String className) throws ClassNotFoundException {
    331         return mMap.get(className);
    332     }
    333 
    334 }
    335