| /* |
| * 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 com.android.ide.common.resources.platform.AttrsXmlParser; |
| import com.android.ide.common.resources.platform.ViewClassInfo; |
| import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.sdk.IAndroidClassLoader.IClassDescriptor; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.SubMonitor; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| |
| import javax.management.InvalidAttributeValueException; |
| |
| /* |
| * TODO: refactor this. Could use some cleanup. |
| */ |
| |
| /** |
| * Parser for the framework library. |
| * <p/> |
| * This gather the following information: |
| * <ul> |
| * <li>Resource ID from <code>android.R</code></li> |
| * <li>The list of permissions values from <code>android.Manifest$permission</code></li> |
| * <li></li> |
| * </ul> |
| */ |
| public class LayoutParamsParser { |
| |
| /** |
| * Class extending {@link ViewClassInfo} by adding the notion of instantiability. |
| * {@link LayoutParamsParser#getViews()} and {@link LayoutParamsParser#getGroups()} should |
| * only return classes that can be instantiated. |
| */ |
| final static class ExtViewClassInfo extends ViewClassInfo { |
| |
| private boolean mIsInstantiable; |
| |
| ExtViewClassInfo(boolean instantiable, boolean isLayout, String canonicalClassName, |
| String shortClassName) { |
| super(isLayout, canonicalClassName, shortClassName); |
| mIsInstantiable = instantiable; |
| } |
| |
| boolean isInstantiable() { |
| return mIsInstantiable; |
| } |
| } |
| |
| /* Note: protected members/methods are overridden in unit tests */ |
| |
| /** Reference to android.view.View */ |
| protected IClassDescriptor mTopViewClass; |
| /** Reference to android.view.ViewGroup */ |
| protected IClassDescriptor mTopGroupClass; |
| /** Reference to android.view.ViewGroup$LayoutParams */ |
| protected IClassDescriptor mTopLayoutParamsClass; |
| |
| /** Input list of all classes deriving from android.view.View */ |
| protected ArrayList<IClassDescriptor> mViewList; |
| /** Input list of all classes deriving from android.view.ViewGroup */ |
| protected ArrayList<IClassDescriptor> mGroupList; |
| |
| /** Output map of FQCN => info on View classes */ |
| protected TreeMap<String, ExtViewClassInfo> mViewMap; |
| /** Output map of FQCN => info on ViewGroup classes */ |
| protected TreeMap<String, ExtViewClassInfo> mGroupMap; |
| /** Output map of FQCN => info on LayoutParams classes */ |
| protected HashMap<String, LayoutParamsInfo> mLayoutParamsMap; |
| |
| /** The attrs.xml parser */ |
| protected AttrsXmlParser mAttrsXmlParser; |
| |
| /** The android.jar class loader */ |
| protected IAndroidClassLoader mClassLoader; |
| |
| /** |
| * Instantiate a new LayoutParamsParser. |
| * @param classLoader The android.jar class loader |
| * @param attrsXmlParser The parser of the attrs.xml file |
| */ |
| public LayoutParamsParser(IAndroidClassLoader classLoader, |
| AttrsXmlParser attrsXmlParser) { |
| mClassLoader = classLoader; |
| mAttrsXmlParser = attrsXmlParser; |
| } |
| |
| /** Returns the map of FQCN => info on View classes */ |
| public List<ViewClassInfo> getViews() { |
| return getInstantiables(mViewMap); |
| } |
| |
| /** Returns the map of FQCN => info on ViewGroup classes */ |
| public List<ViewClassInfo> getGroups() { |
| return getInstantiables(mGroupMap); |
| } |
| |
| /** |
| * TODO: doc here. |
| * <p/> |
| * Note: on output we should have NO dependency on {@link IClassDescriptor}, |
| * otherwise we wouldn't be able to unload the class loader later. |
| * <p/> |
| * Note on Vocabulary: FQCN=Fully Qualified Class Name (e.g. "my.package.class$innerClass") |
| * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. |
| */ |
| public void parseLayoutClasses(IProgressMonitor monitor) { |
| parseClasses(monitor, |
| SdkConstants.CLASS_VIEW, |
| SdkConstants.CLASS_VIEWGROUP, |
| SdkConstants.CLASS_VIEWGROUP_LAYOUTPARAMS); |
| } |
| |
| public void parsePreferencesClasses(IProgressMonitor monitor) { |
| parseClasses(monitor, |
| SdkConstants.CLASS_PREFERENCE, |
| SdkConstants.CLASS_PREFERENCEGROUP, |
| null /* paramsClassName */ ); |
| } |
| |
| private void parseClasses(IProgressMonitor monitor, |
| String rootClassName, |
| String groupClassName, |
| String paramsClassName) { |
| try { |
| SubMonitor progress = SubMonitor.convert(monitor, 100); |
| |
| String[] superClasses = new String[2 + (paramsClassName == null ? 0 : 1)]; |
| superClasses[0] = groupClassName; |
| superClasses[1] = rootClassName; |
| if (paramsClassName != null) { |
| superClasses[2] = paramsClassName; |
| } |
| HashMap<String, ArrayList<IClassDescriptor>> found = |
| mClassLoader.findClassesDerivingFrom("android.", superClasses); //$NON-NLS-1$ |
| mTopViewClass = mClassLoader.getClass(rootClassName); |
| mTopGroupClass = mClassLoader.getClass(groupClassName); |
| if (paramsClassName != null) { |
| mTopLayoutParamsClass = mClassLoader.getClass(paramsClassName); |
| } |
| |
| mViewList = found.get(rootClassName); |
| mGroupList = found.get(groupClassName); |
| |
| mViewMap = new TreeMap<String, ExtViewClassInfo>(); |
| mGroupMap = new TreeMap<String, ExtViewClassInfo>(); |
| if (mTopLayoutParamsClass != null) { |
| mLayoutParamsMap = new HashMap<String, LayoutParamsInfo>(); |
| } |
| |
| // Add top classes to the maps since by design they are not listed in classes deriving |
| // from themselves. |
| if (mTopGroupClass != null) { |
| addGroup(mTopGroupClass); |
| } |
| if (mTopViewClass != null) { |
| addView(mTopViewClass); |
| } |
| |
| // ViewGroup derives from View |
| ExtViewClassInfo vg = mGroupMap.get(groupClassName); |
| if (vg != null) { |
| vg.setSuperClass(mViewMap.get(rootClassName)); |
| } |
| |
| progress.setWorkRemaining(mGroupList.size() + mViewList.size()); |
| |
| for (IClassDescriptor groupChild : mGroupList) { |
| addGroup(groupChild); |
| progress.worked(1); |
| } |
| |
| for (IClassDescriptor viewChild : mViewList) { |
| if (viewChild != mTopGroupClass) { |
| addView(viewChild); |
| } |
| progress.worked(1); |
| } |
| } catch (ClassNotFoundException e) { |
| AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$ |
| rootClassName, groupClassName); |
| } catch (InvalidAttributeValueException e) { |
| AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ |
| } catch (ClassFormatError e) { |
| AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ |
| } catch (IOException e) { |
| AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Parses a View class and adds a ExtViewClassInfo for it in mViewMap. |
| * It calls itself recursively to handle super classes which are also Views. |
| */ |
| private ExtViewClassInfo addView(IClassDescriptor viewClass) { |
| String fqcn = viewClass.getFullClassName(); |
| if (mViewMap.containsKey(fqcn)) { |
| return mViewMap.get(fqcn); |
| } else if (mGroupMap.containsKey(fqcn)) { |
| return mGroupMap.get(fqcn); |
| } |
| |
| ExtViewClassInfo info = new ExtViewClassInfo(viewClass.isInstantiable(), |
| false /* layout */, fqcn, viewClass.getSimpleName()); |
| mViewMap.put(fqcn, info); |
| |
| // All view classes derive from mTopViewClass by design. |
| // Do not lookup the super class for mTopViewClass itself. |
| if (viewClass.equals(mTopViewClass) == false) { |
| IClassDescriptor superClass = viewClass.getSuperclass(); |
| ExtViewClassInfo superClassInfo = addView(superClass); |
| info.setSuperClass(superClassInfo); |
| } |
| |
| mAttrsXmlParser.loadViewAttributes(info); |
| return info; |
| } |
| |
| /** |
| * Parses a ViewGroup class and adds a ExtViewClassInfo for it in mGroupMap. |
| * It calls itself recursively to handle super classes which are also ViewGroups. |
| */ |
| private ExtViewClassInfo addGroup(IClassDescriptor groupClass) { |
| String fqcn = groupClass.getFullClassName(); |
| if (mGroupMap.containsKey(fqcn)) { |
| return mGroupMap.get(fqcn); |
| } |
| |
| ExtViewClassInfo info = new ExtViewClassInfo(groupClass.isInstantiable(), |
| true /* layout */, fqcn, groupClass.getSimpleName()); |
| mGroupMap.put(fqcn, info); |
| |
| // All groups derive from android.view.ViewGroup, which in turns derives from |
| // android.view.View (i.e. mTopViewClass here). So the only group that can have View as |
| // its super class is the ViewGroup base class and we don't try to resolve it since groups |
| // are loaded before views. |
| IClassDescriptor superClass = groupClass.getSuperclass(); |
| |
| // Assertion: at this point, we should have |
| // superClass != mTopViewClass || fqcn.equals(SdkConstants.CLASS_VIEWGROUP); |
| |
| if (superClass != null && superClass.equals(mTopViewClass) == false) { |
| ExtViewClassInfo superClassInfo = addGroup(superClass); |
| |
| // Assertion: we should have superClassInfo != null && superClassInfo != info; |
| if (superClassInfo != null && superClassInfo != info) { |
| info.setSuperClass(superClassInfo); |
| } |
| } |
| |
| mAttrsXmlParser.loadViewAttributes(info); |
| if (mTopLayoutParamsClass != null) { |
| info.setLayoutParams(addLayoutParams(groupClass)); |
| } |
| return info; |
| } |
| |
| /** |
| * Parses a ViewGroup class and returns an info object on its inner LayoutParams. |
| * |
| * @return The {@link LayoutParamsInfo} for the ViewGroup class or null. |
| */ |
| private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) { |
| |
| // Is there a LayoutParams in this group class? |
| IClassDescriptor layoutParamsClass = findLayoutParams(groupClass); |
| |
| // if there's no layout data in the group class, link to the one from the |
| // super class. |
| if (layoutParamsClass == null) { |
| for (IClassDescriptor superClass = groupClass.getSuperclass(); |
| layoutParamsClass == null && |
| superClass != null && |
| superClass.equals(mTopViewClass) == false; |
| superClass = superClass.getSuperclass()) { |
| layoutParamsClass = findLayoutParams(superClass); |
| } |
| } |
| |
| if (layoutParamsClass != null) { |
| return getLayoutParamsInfo(layoutParamsClass); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Parses a LayoutParams class and returns a LayoutParamsInfo object for it. |
| * It calls itself recursively to handle the super class of the LayoutParams. |
| */ |
| private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) { |
| String fqcn = layoutParamsClass.getFullClassName(); |
| LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn); |
| |
| if (layoutParamsInfo != null) { |
| return layoutParamsInfo; |
| } |
| |
| // Find the link on the LayoutParams super class |
| LayoutParamsInfo superClassInfo = null; |
| if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) { |
| IClassDescriptor superClass = layoutParamsClass.getSuperclass(); |
| superClassInfo = getLayoutParamsInfo(superClass); |
| } |
| |
| // Find the link on the enclosing ViewGroup |
| ExtViewClassInfo enclosingGroupInfo = addGroup(layoutParamsClass.getEnclosingClass()); |
| |
| layoutParamsInfo = new ExtViewClassInfo.LayoutParamsInfo( |
| enclosingGroupInfo, layoutParamsClass.getSimpleName(), superClassInfo); |
| mLayoutParamsMap.put(fqcn, layoutParamsInfo); |
| |
| mAttrsXmlParser.loadLayoutParamsAttributes(layoutParamsInfo); |
| |
| return layoutParamsInfo; |
| } |
| |
| /** |
| * Given a ViewGroup-derived class, looks for an inner class named LayoutParams |
| * and if found returns its class definition. |
| * <p/> |
| * This uses the actual defined inner classes and does not look at inherited classes. |
| * |
| * @param groupClass The ViewGroup derived class |
| * @return The Class of the inner LayoutParams or null if none is declared. |
| */ |
| private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) { |
| IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses(); |
| for (IClassDescriptor innerClass : innerClasses) { |
| if (innerClass.getSimpleName().equals(SdkConstants.CLASS_NAME_LAYOUTPARAMS)) { |
| return innerClass; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Computes and return a list of ViewClassInfo from a map by filtering out the class that |
| * cannot be instantiated. |
| */ |
| private List<ViewClassInfo> getInstantiables(SortedMap<String, ExtViewClassInfo> map) { |
| Collection<ExtViewClassInfo> values = map.values(); |
| ArrayList<ViewClassInfo> list = new ArrayList<ViewClassInfo>(); |
| |
| for (ExtViewClassInfo info : values) { |
| if (info.isInstantiable()) { |
| list.add(info); |
| } |
| } |
| |
| return list; |
| } |
| } |