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