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