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