| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ide.eclipse.adt.internal.sdk; |
| |
| import com.android.SdkConstants; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| |
| import java.io.BufferedReader; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import javax.management.InvalidAttributeValueException; |
| |
| /** |
| * Parser for the text file containing the list of widgets, layouts and layout params. |
| * <p/> |
| * The file is a straight text file containing one class per line.<br> |
| * Each line is in the following format<br> |
| * <code>[code][class name] [super class name] [super class name]...</code> |
| * where code is a single letter (W for widget, L for layout, P for layout params), and class names |
| * are the fully qualified name of the classes. |
| */ |
| public final class WidgetClassLoader implements IAndroidClassLoader { |
| |
| /** |
| * Basic class containing the class descriptions found in the text file. |
| */ |
| private final static class ClassDescriptor implements IClassDescriptor { |
| |
| private String mFqcn; |
| private String mSimpleName; |
| private ClassDescriptor mSuperClass; |
| private ClassDescriptor mEnclosingClass; |
| private final ArrayList<IClassDescriptor> mDeclaredClasses = |
| new ArrayList<IClassDescriptor>(); |
| private boolean mIsInstantiable = false; |
| |
| ClassDescriptor(String fqcn) { |
| mFqcn = fqcn; |
| mSimpleName = getSimpleName(fqcn); |
| } |
| |
| @Override |
| public String getFullClassName() { |
| return mFqcn; |
| } |
| |
| @Override |
| public String getSimpleName() { |
| return mSimpleName; |
| } |
| |
| @Override |
| public IClassDescriptor[] getDeclaredClasses() { |
| return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]); |
| } |
| |
| private void addDeclaredClass(ClassDescriptor declaredClass) { |
| mDeclaredClasses.add(declaredClass); |
| } |
| |
| @Override |
| public IClassDescriptor getEnclosingClass() { |
| return mEnclosingClass; |
| } |
| |
| void setEnclosingClass(ClassDescriptor enclosingClass) { |
| // set the enclosing class. |
| mEnclosingClass = enclosingClass; |
| |
| // add this to the list of declared class in the enclosing class. |
| mEnclosingClass.addDeclaredClass(this); |
| |
| // finally change the name of declared class to make sure it uses the |
| // convention: package.enclosing$declared instead of package.enclosing.declared |
| mFqcn = enclosingClass.mFqcn + "$" + mFqcn.substring(enclosingClass.mFqcn.length() + 1); |
| } |
| |
| @Override |
| public IClassDescriptor getSuperclass() { |
| return mSuperClass; |
| } |
| |
| void setSuperClass(ClassDescriptor superClass) { |
| mSuperClass = superClass; |
| } |
| |
| @Override |
| public boolean equals(Object clazz) { |
| if (clazz instanceof ClassDescriptor) { |
| return mFqcn.equals(((ClassDescriptor)clazz).mFqcn); |
| } |
| return super.equals(clazz); |
| } |
| |
| @Override |
| public int hashCode() { |
| return mFqcn.hashCode(); |
| } |
| |
| @Override |
| public boolean isInstantiable() { |
| return mIsInstantiable; |
| } |
| |
| void setInstantiable(boolean state) { |
| mIsInstantiable = state; |
| } |
| |
| private String getSimpleName(String fqcn) { |
| String[] segments = fqcn.split("\\."); |
| return segments[segments.length-1]; |
| } |
| } |
| |
| private BufferedReader mReader; |
| |
| /** Output map of FQCN => descriptor on all classes */ |
| private final Map<String, ClassDescriptor> mMap = new TreeMap<String, ClassDescriptor>(); |
| /** Output map of FQCN => descriptor on View classes */ |
| private final Map<String, ClassDescriptor> mWidgetMap = new TreeMap<String, ClassDescriptor>(); |
| /** Output map of FQCN => descriptor on ViewGroup classes */ |
| private final Map<String, ClassDescriptor> mLayoutMap = new TreeMap<String, ClassDescriptor>(); |
| /** Output map of FQCN => descriptor on LayoutParams classes */ |
| private final Map<String, ClassDescriptor> mLayoutParamsMap = |
| new HashMap<String, ClassDescriptor>(); |
| /** File path of the source text file */ |
| private String mOsFilePath; |
| |
| /** |
| * Creates a loader with a given file path. |
| * @param osFilePath the OS path of the file to load. |
| * @throws FileNotFoundException if the file is not found. |
| */ |
| WidgetClassLoader(String osFilePath) throws FileNotFoundException { |
| mOsFilePath = osFilePath; |
| mReader = new BufferedReader(new FileReader(osFilePath)); |
| } |
| |
| @Override |
| public String getSource() { |
| return mOsFilePath; |
| } |
| |
| /** |
| * Parses the text file and return true if the file was successfully parsed. |
| * @param monitor |
| */ |
| boolean parseWidgetList(IProgressMonitor monitor) { |
| try { |
| String line; |
| while ((line = mReader.readLine()) != null) { |
| if (line.length() > 0) { |
| char prefix = line.charAt(0); |
| String[] classes = null; |
| ClassDescriptor clazz = null; |
| switch (prefix) { |
| case 'W': |
| classes = line.substring(1).split(" "); |
| clazz = processClass(classes, 0, null /* map */); |
| if (clazz != null) { |
| clazz.setInstantiable(true); |
| mWidgetMap.put(classes[0], clazz); |
| } |
| break; |
| case 'L': |
| classes = line.substring(1).split(" "); |
| clazz = processClass(classes, 0, null /* map */); |
| if (clazz != null) { |
| clazz.setInstantiable(true); |
| mLayoutMap.put(classes[0], clazz); |
| } |
| break; |
| case 'P': |
| classes = line.substring(1).split(" "); |
| clazz = processClass(classes, 0, mLayoutParamsMap); |
| if (clazz != null) { |
| clazz.setInstantiable(true); |
| } |
| break; |
| case '#': |
| // comment, do nothing |
| break; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| } |
| } |
| |
| // reconciliate the layout and their layout params |
| postProcess(); |
| |
| return true; |
| } catch (IOException e) { |
| } finally { |
| try { |
| mReader.close(); |
| } catch (IOException e) { |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Parses a View class and adds a ViewClassInfo for it in mWidgetMap. |
| * It calls itself recursively to handle super classes which are also Views. |
| * @param classes the inheritance list of the class to process. |
| * @param index the index of the class to process in the <code>classes</code> array. |
| * @param map an optional map in which to put every {@link ClassDescriptor} created. |
| */ |
| private ClassDescriptor processClass(String[] classes, int index, |
| Map<String, ClassDescriptor> map) { |
| if (index >= classes.length) { |
| return null; |
| } |
| |
| String fqcn = classes[index]; |
| |
| if ("java.lang.Object".equals(fqcn)) { //$NON-NLS-1$ |
| return null; |
| } |
| |
| // check if the ViewInfoClass has not yet been created. |
| if (mMap.containsKey(fqcn)) { |
| return mMap.get(fqcn); |
| } |
| |
| // create the custom class. |
| ClassDescriptor clazz = new ClassDescriptor(fqcn); |
| mMap.put(fqcn, clazz); |
| if (map != null) { |
| map.put(fqcn, clazz); |
| } |
| |
| // get the super class |
| ClassDescriptor superClass = processClass(classes, index+1, map); |
| if (superClass != null) { |
| clazz.setSuperClass(superClass); |
| } |
| |
| return clazz; |
| } |
| |
| /** |
| * Goes through the layout params and look for the enclosed class. If the layout params |
| * has no known enclosed type it is dropped. |
| */ |
| private void postProcess() { |
| Collection<ClassDescriptor> params = mLayoutParamsMap.values(); |
| |
| for (ClassDescriptor param : params) { |
| String fqcn = param.getFullClassName(); |
| |
| // get the enclosed name. |
| String enclosed = getEnclosedName(fqcn); |
| |
| // look for a match in the layouts. We don't use the layout map as it only contains the |
| // end classes, but in this case we also need to process the layout params for the base |
| // layout classes. |
| ClassDescriptor enclosingType = mMap.get(enclosed); |
| if (enclosingType != null) { |
| param.setEnclosingClass(enclosingType); |
| |
| // remove the class from the map, and put it back with the fixed name |
| mMap.remove(fqcn); |
| mMap.put(param.getFullClassName(), param); |
| } |
| } |
| } |
| |
| private String getEnclosedName(String fqcn) { |
| int index = fqcn.lastIndexOf('.'); |
| return fqcn.substring(0, index); |
| } |
| |
| /** |
| * Finds and loads all classes that derive from a given set of super classes. |
| * |
| * @param rootPackage Root package of classes to find. Use an empty string to find everyting. |
| * @param superClasses The super classes of all the classes to find. |
| * @return An hash map which keys are the super classes looked for and which values are |
| * ArrayList of the classes found. The array lists are always created for all the |
| * valid keys, they are simply empty if no deriving class is found for a given |
| * super class. |
| * @throws IOException |
| * @throws InvalidAttributeValueException |
| * @throws ClassFormatError |
| */ |
| @Override |
| public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage, |
| String[] superClasses) throws IOException, InvalidAttributeValueException, |
| ClassFormatError { |
| HashMap<String, ArrayList<IClassDescriptor>> map = |
| new HashMap<String, ArrayList<IClassDescriptor>>(); |
| |
| ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>(); |
| list.addAll(mWidgetMap.values()); |
| map.put(SdkConstants.CLASS_VIEW, list); |
| |
| list = new ArrayList<IClassDescriptor>(); |
| list.addAll(mLayoutMap.values()); |
| map.put(SdkConstants.CLASS_VIEWGROUP, list); |
| |
| list = new ArrayList<IClassDescriptor>(); |
| list.addAll(mLayoutParamsMap.values()); |
| map.put(SdkConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list); |
| |
| return map; |
| } |
| |
| /** |
| * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name. |
| * @param className the fully-qualified name of the class to return. |
| * @throws ClassNotFoundException |
| */ |
| @Override |
| public IClassDescriptor getClass(String className) throws ClassNotFoundException { |
| return mMap.get(className); |
| } |
| |
| } |