diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java | 693 |
1 files changed, 0 insertions, 693 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java deleted file mode 100644 index 020c666b9..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java +++ /dev/null @@ -1,693 +0,0 @@ -/* - * 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.editors.layout; - -import static com.android.SdkConstants.ANDROID_PKG_PREFIX; -import static com.android.SdkConstants.CALENDAR_VIEW; -import static com.android.SdkConstants.CLASS_VIEW; -import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; -import static com.android.SdkConstants.FQCN_GRID_VIEW; -import static com.android.SdkConstants.FQCN_SPINNER; -import static com.android.SdkConstants.GRID_VIEW; -import static com.android.SdkConstants.LIST_VIEW; -import static com.android.SdkConstants.SPINNER; -import static com.android.SdkConstants.VIEW_FRAGMENT; -import static com.android.SdkConstants.VIEW_INCLUDE; - -import com.android.SdkConstants; -import com.android.ide.common.rendering.LayoutLibrary; -import com.android.ide.common.rendering.RenderSecurityManager; -import com.android.ide.common.rendering.api.ActionBarCallback; -import com.android.ide.common.rendering.api.AdapterBinding; -import com.android.ide.common.rendering.api.DataBindingItem; -import com.android.ide.common.rendering.api.Features; -import com.android.ide.common.rendering.api.ILayoutPullParser; -import com.android.ide.common.rendering.api.IProjectCallback; -import com.android.ide.common.rendering.api.LayoutlibCallback; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.ide.common.rendering.api.ResourceReference; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.rendering.api.Result; -import com.android.ide.common.resources.ResourceResolver; -import com.android.ide.common.xml.ManifestData; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderLogger; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectClassLoader; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.resources.ResourceType; -import com.android.util.Pair; -import com.google.common.base.Charsets; -import com.google.common.io.Files; - -import org.eclipse.core.resources.IProject; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.StringReader; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -/** - * Loader for Android Project class in order to use them in the layout editor. - * <p/>This implements {@link IProjectCallback} for the old and new API through - * {@link LayoutlibCallback} - */ -public final class ProjectCallback extends LayoutlibCallback { - private final HashMap<String, Class<?>> mLoadedClasses = new HashMap<String, Class<?>>(); - private final Set<String> mMissingClasses = new TreeSet<String>(); - private final Set<String> mBrokenClasses = new TreeSet<String>(); - private final IProject mProject; - private final ClassLoader mParentClassLoader; - private final ProjectResources mProjectRes; - private final Object mCredential; - private boolean mUsed = false; - private String mNamespace; - private ProjectClassLoader mLoader = null; - private LayoutLog mLogger; - private LayoutLibrary mLayoutLib; - private String mLayoutName; - private ILayoutPullParser mLayoutEmbeddedParser; - private ResourceResolver mResourceResolver; - private GraphicalEditorPart mEditor; - - /** - * Creates a new {@link ProjectCallback} to be used with the layout lib. - * - * @param layoutLib The layout library this callback is going to be invoked from - * @param projectRes the {@link ProjectResources} for the project. - * @param project the project. - * @param credential the sandbox credential - */ - public ProjectCallback(LayoutLibrary layoutLib, - ProjectResources projectRes, IProject project, Object credential, - GraphicalEditorPart editor) { - mLayoutLib = layoutLib; - mParentClassLoader = layoutLib.getClassLoader(); - mProjectRes = projectRes; - mProject = project; - mCredential = credential; - mEditor = editor; - } - - public Set<String> getMissingClasses() { - return mMissingClasses; - } - - public Set<String> getUninstantiatableClasses() { - return mBrokenClasses; - } - - /** - * Sets the {@link LayoutLog} logger to use for error messages during problems - * - * @param logger the new logger to use, or null to clear it out - */ - public void setLogger(LayoutLog logger) { - mLogger = logger; - } - - /** - * Returns the {@link LayoutLog} logger used for error messages, or null - * - * @return the logger being used, or null if no logger is in use - */ - public LayoutLog getLogger() { - return mLogger; - } - - /** - * {@inheritDoc} - * - * This implementation goes through the output directory of the Eclipse project and loads the - * <code>.class</code> file directly. - */ - @Override - @SuppressWarnings("unchecked") - public Object loadView(String className, Class[] constructorSignature, - Object[] constructorParameters) - throws Exception { - mUsed = true; - - if (className == null) { - // Just make a plain <View> if you specify <view> without a class= attribute. - className = CLASS_VIEW; - } - - // look for a cached version - Class<?> clazz = mLoadedClasses.get(className); - if (clazz != null) { - return instantiateClass(clazz, constructorSignature, constructorParameters); - } - - // load the class. - - try { - if (mLoader == null) { - // Allow creating class loaders during rendering; may be prevented by the - // RenderSecurityManager - boolean token = RenderSecurityManager.enterSafeRegion(mCredential); - try { - mLoader = new ProjectClassLoader(mParentClassLoader, mProject); - } finally { - RenderSecurityManager.exitSafeRegion(token); - } - } - clazz = mLoader.loadClass(className); - } catch (Exception e) { - // Add the missing class to the list so that the renderer can print them later. - // no need to log this. - if (!className.equals(VIEW_FRAGMENT) && !className.equals(VIEW_INCLUDE)) { - mMissingClasses.add(className); - } - } - - try { - if (clazz != null) { - // first try to instantiate it because adding it the list of loaded class so that - // we don't add broken classes. - Object view = instantiateClass(clazz, constructorSignature, constructorParameters); - mLoadedClasses.put(className, clazz); - - return view; - } - } catch (Throwable e) { - // Find root cause to log it. - while (e.getCause() != null) { - e = e.getCause(); - } - - appendToIdeLog(e, "%1$s failed to instantiate.", className); //$NON-NLS-1$ - - // Add the missing class to the list so that the renderer can print them later. - if (mLogger instanceof RenderLogger) { - RenderLogger renderLogger = (RenderLogger) mLogger; - renderLogger.recordThrowable(e); - - } - mBrokenClasses.add(className); - } - - // Create a mock view instead. We don't cache it in the mLoadedClasses map. - // If any exception is thrown, we'll return a CFN with the original class name instead. - try { - clazz = mLoader.loadClass(SdkConstants.CLASS_MOCK_VIEW); - Object view = instantiateClass(clazz, constructorSignature, constructorParameters); - - // Set the text of the mock view to the simplified name of the custom class - Method m = view.getClass().getMethod("setText", - new Class<?>[] { CharSequence.class }); - String label = getShortClassName(className); - if (label.equals(VIEW_FRAGMENT)) { - label = "<fragment>\n" - + "Pick preview layout from the \"Fragment Layout\" context menu"; - } else if (label.equals(VIEW_INCLUDE)) { - label = "Text"; - } - - m.invoke(view, label); - - // Call MockView.setGravity(Gravity.CENTER) to get the text centered in - // MockViews. - // TODO: Do this in layoutlib's MockView class instead. - try { - // Look up android.view.Gravity#CENTER - or can we just hard-code - // the value (17) here? - Class<?> gravity = - Class.forName("android.view.Gravity", //$NON-NLS-1$ - true, view.getClass().getClassLoader()); - Field centerField = gravity.getField("CENTER"); //$NON-NLS-1$ - int center = centerField.getInt(null); - m = view.getClass().getMethod("setGravity", - new Class<?>[] { Integer.TYPE }); - // Center - //int center = (0x0001 << 4) | (0x0001 << 0); - m.invoke(view, Integer.valueOf(center)); - } catch (Exception e) { - // Not important to center views - } - - return view; - } catch (Exception e) { - // We failed to create and return a mock view. - // Just throw back a CNF with the original class name. - throw new ClassNotFoundException(className, e); - } - } - - private String getShortClassName(String fqcn) { - // The name is typically a fully-qualified class name. Let's make it a tad shorter. - - if (fqcn.startsWith("android.")) { //$NON-NLS-1$ - // For android classes, convert android.foo.Name to android...Name - int first = fqcn.indexOf('.'); - int last = fqcn.lastIndexOf('.'); - if (last > first) { - return fqcn.substring(0, first) + ".." + fqcn.substring(last); //$NON-NLS-1$ - } - } else { - // For custom non-android classes, it's best to keep the 2 first segments of - // the namespace, e.g. we want to get something like com.example...MyClass - int first = fqcn.indexOf('.'); - first = fqcn.indexOf('.', first + 1); - int last = fqcn.lastIndexOf('.'); - if (last > first) { - return fqcn.substring(0, first) + ".." + fqcn.substring(last); //$NON-NLS-1$ - } - } - - return fqcn; - } - - /** - * Returns the namespace for the project. The namespace contains a standard part + the - * application package. - * - * @return The package namespace of the project or null in case of error. - */ - @Override - public String getNamespace() { - if (mNamespace == null) { - boolean token = RenderSecurityManager.enterSafeRegion(mCredential); - try { - ManifestData manifestData = AndroidManifestHelper.parseForData(mProject); - if (manifestData != null) { - String javaPackage = manifestData.getPackage(); - mNamespace = String.format(AdtConstants.NS_CUSTOM_RESOURCES, javaPackage); - } - } finally { - RenderSecurityManager.exitSafeRegion(token); - } - } - - return mNamespace; - } - - @Override - public Pair<ResourceType, String> resolveResourceId(int id) { - if (mProjectRes != null) { - return mProjectRes.resolveResourceId(id); - } - - return null; - } - - @Override - public String resolveResourceId(int[] id) { - if (mProjectRes != null) { - return mProjectRes.resolveStyleable(id); - } - - return null; - } - - @Override - public Integer getResourceId(ResourceType type, String name) { - if (mProjectRes != null) { - return mProjectRes.getResourceId(type, name); - } - - return null; - } - - /** - * Returns whether the loader has received requests to load custom views. Note that - * the custom view loading may not actually have succeeded; this flag only records - * whether it was <b>requested</b>. - * <p/> - * This allows to efficiently only recreate when needed upon code change in the - * project. - * - * @return true if the loader has been asked to load custom views - */ - public boolean isUsed() { - return mUsed; - } - - /** - * Instantiate a class object, using a specific constructor and parameters. - * @param clazz the class to instantiate - * @param constructorSignature the signature of the constructor to use - * @param constructorParameters the parameters to use in the constructor. - * @return A new class object, created using a specific constructor and parameters. - * @throws Exception - */ - @SuppressWarnings("unchecked") - private Object instantiateClass(Class<?> clazz, - Class[] constructorSignature, - Object[] constructorParameters) throws Exception { - Constructor<?> constructor = null; - - try { - constructor = clazz.getConstructor(constructorSignature); - - } catch (NoSuchMethodException e) { - // Custom views can either implement a 3-parameter, 2-parameter or a - // 1-parameter. Let's synthetically build and try all the alternatives. - // That's kind of like switching to the other box. - // - // The 3-parameter constructor takes the following arguments: - // ...(Context context, AttributeSet attrs, int defStyle) - - int n = constructorSignature.length; - if (n == 0) { - // There is no parameter-less constructor. Nobody should ask for one. - throw e; - } - - for (int i = 3; i >= 1; i--) { - if (i == n) { - // Let's skip the one we know already fails - continue; - } - Class[] sig = new Class[i]; - Object[] params = new Object[i]; - - int k = i; - if (n < k) { - k = n; - } - System.arraycopy(constructorSignature, 0, sig, 0, k); - System.arraycopy(constructorParameters, 0, params, 0, k); - - for (k++; k <= i; k++) { - if (k == 2) { - // Parameter 2 is the AttributeSet - sig[k-1] = clazz.getClassLoader().loadClass("android.util.AttributeSet"); - params[k-1] = null; - - } else if (k == 3) { - // Parameter 3 is the int defstyle - sig[k-1] = int.class; - params[k-1] = 0; - } - } - - constructorSignature = sig; - constructorParameters = params; - - try { - // Try again... - constructor = clazz.getConstructor(constructorSignature); - if (constructor != null) { - // Found a suitable constructor, now let's use it. - // (But let's warn the user if the simple View constructor was found - // since Unexpected Things may happen if the attribute set constructors - // are not found) - if (constructorSignature.length < 2 && mLogger != null) { - mLogger.warning("wrongconstructor", //$NON-NLS-1$ - String.format("Custom view %1$s is not using the 2- or 3-argument " - + "View constructors; XML attributes will not work", - clazz.getSimpleName()), null /*data*/); - } - break; - } - } catch (NoSuchMethodException e1) { - // pass - } - } - - // If all the alternatives failed, throw the initial exception. - if (constructor == null) { - throw e; - } - } - - constructor.setAccessible(true); - return constructor.newInstance(constructorParameters); - } - - public void setLayoutParser(String layoutName, ILayoutPullParser layoutParser) { - mLayoutName = layoutName; - mLayoutEmbeddedParser = layoutParser; - } - - @Override - public ILayoutPullParser getParser(String layoutName) { - boolean token = RenderSecurityManager.enterSafeRegion(mCredential); - try { - // Try to compute the ResourceValue for this layout since layoutlib - // must be an older version which doesn't pass the value: - if (mResourceResolver != null) { - ResourceValue value = mResourceResolver.getProjectResource(ResourceType.LAYOUT, - layoutName); - if (value != null) { - return getParser(value); - } - } - - return getParser(layoutName, null); - } finally { - RenderSecurityManager.exitSafeRegion(token); - } - } - - @Override - public ILayoutPullParser getParser(ResourceValue layoutResource) { - boolean token = RenderSecurityManager.enterSafeRegion(mCredential); - try { - return getParser(layoutResource.getName(), - new File(layoutResource.getValue())); - } finally { - RenderSecurityManager.exitSafeRegion(token); - } - } - - private ILayoutPullParser getParser(String layoutName, File xml) { - if (layoutName.equals(mLayoutName)) { - ILayoutPullParser parser = mLayoutEmbeddedParser; - // The parser should only be used once!! If it is included more than once, - // subsequent includes should just use a plain pull parser that is not tied - // to the XML model - mLayoutEmbeddedParser = null; - return parser; - } - - // For included layouts, create a ContextPullParser such that we get the - // layout editor behavior in included layouts as well - which for example - // replaces <fragment> tags with <include>. - if (xml != null && xml.isFile()) { - ContextPullParser parser = new ContextPullParser(this, xml); - try { - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - String xmlText = Files.toString(xml, Charsets.UTF_8); - parser.setInput(new StringReader(xmlText)); - return parser; - } catch (XmlPullParserException e) { - appendToIdeLog(e, null); - } catch (FileNotFoundException e) { - // Shouldn't happen since we check isFile() above - } catch (IOException e) { - appendToIdeLog(e, null); - } - } - - return null; - } - - @Override - public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, - ResourceReference itemRef, - int fullPosition, int typePosition, int fullChildPosition, int typeChildPosition, - ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) { - - // Special case for the palette preview - if (viewAttribute == ViewAttribute.TEXT - && adapterView.getName().startsWith("android_widget_")) { //$NON-NLS-1$ - String name = adapterView.getName(); - if (viewRef.getName().equals("text2")) { //$NON-NLS-1$ - return "Sub Item"; - } - if (fullPosition == 0) { - String viewName = name.substring("android_widget_".length()); - if (viewName.equals(EXPANDABLE_LIST_VIEW)) { - return "ExpandableList"; // ExpandableListView is too wide, character-wraps - } - return viewName; - } else { - return "Next Item"; - } - } - - if (itemRef.isFramework()) { - // Special case for list_view_item_2 and friends - if (viewRef.getName().equals("text2")) { //$NON-NLS-1$ - return "Sub Item " + (fullPosition + 1); - } - } - - if (viewAttribute == ViewAttribute.TEXT && ((String) defaultValue).length() == 0) { - return "Item " + (fullPosition + 1); - } - - return null; - } - - /** - * For the given class, finds and returns the nearest super class which is a ListView - * or an ExpandableListView or a GridView (which uses a list adapter), or returns null. - * - * @param clz the class of the view object - * @return the fully qualified class name of the list ancestor, or null if there - * is no list view ancestor - */ - public static String getListAdapterViewFqcn(Class<?> clz) { - String fqcn = clz.getName(); - if (fqcn.endsWith(LIST_VIEW)) { // including EXPANDABLE_LIST_VIEW - return fqcn; - } else if (fqcn.equals(FQCN_GRID_VIEW)) { - return fqcn; - } else if (fqcn.equals(FQCN_SPINNER)) { - return fqcn; - } else if (fqcn.startsWith(ANDROID_PKG_PREFIX)) { - return null; - } - Class<?> superClass = clz.getSuperclass(); - if (superClass != null) { - return getListAdapterViewFqcn(superClass); - } else { - // Should not happen; we would have encountered android.view.View first, - // and it should have been covered by the ANDROID_PKG_PREFIX case above. - return null; - } - } - - /** - * Looks at the parent-chain of the view and if it finds a custom view, or a - * CalendarView, within the given distance then it returns true. A ListView within a - * CalendarView should not be assigned a custom list view type because it sets its own - * and then attempts to cast the layout to its own type which would fail if the normal - * default list item binding is used. - */ - private boolean isWithinIllegalParent(Object viewObject, int depth) { - String fqcn = viewObject.getClass().getName(); - if (fqcn.endsWith(CALENDAR_VIEW) || !fqcn.startsWith(ANDROID_PKG_PREFIX)) { - return true; - } - - if (depth > 0) { - Result result = mLayoutLib.getViewParent(viewObject); - if (result.isSuccess()) { - Object parent = result.getData(); - if (parent != null) { - return isWithinIllegalParent(parent, depth -1); - } - } - } - - return false; - } - - @Override - public AdapterBinding getAdapterBinding(final ResourceReference adapterView, - final Object adapterCookie, final Object viewObject) { - // Look for user-recorded preference for layout to be used for previews - if (adapterCookie instanceof UiViewElementNode) { - UiViewElementNode uiNode = (UiViewElementNode) adapterCookie; - AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, uiNode); - if (binding != null) { - return binding; - } - } else if (adapterCookie instanceof Map<?,?>) { - @SuppressWarnings("unchecked") - Map<String, String> map = (Map<String, String>) adapterCookie; - AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, map); - if (binding != null) { - return binding; - } - } - - if (viewObject == null) { - return null; - } - - // Is this a ListView or ExpandableListView? If so, return its fully qualified - // class name, otherwise return null. This is used to filter out other types - // of AdapterViews (such as Spinners) where we don't want to use the list item - // binding. - String listFqcn = getListAdapterViewFqcn(viewObject.getClass()); - if (listFqcn == null) { - return null; - } - - // Is this ListView nested within an "illegal" container, such as a CalendarView? - // If so, don't change the bindings below. Some views, such as CalendarView, and - // potentially some custom views, might be doing specific things with the ListView - // that could break if we add our own list binding, so for these leave the list - // alone. - if (isWithinIllegalParent(viewObject, 2)) { - return null; - } - - int count = listFqcn.endsWith(GRID_VIEW) ? 24 : 12; - AdapterBinding binding = new AdapterBinding(count); - if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) { - binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_EXPANDABLE_LIST_ITEM, - true /* isFramework */, 1)); - } else if (listFqcn.equals(SPINNER)) { - binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_SPINNER_ITEM, - true /* isFramework */, 1)); - } else { - binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_LIST_ITEM, - true /* isFramework */, 1)); - } - - return binding; - } - - /** - * Sets the {@link ResourceResolver} to be used when looking up resources - * - * @param resolver the resolver to use - */ - public void setResourceResolver(ResourceResolver resolver) { - mResourceResolver = resolver; - } - - // Append the given message to the ADT log. Bypass the sandbox if necessary - // such that we can write to the log file. - private void appendToIdeLog(Throwable exception, String format, Object ... args) { - boolean token = RenderSecurityManager.enterSafeRegion(mCredential); - try { - AdtPlugin.log(exception, format, args); - } finally { - RenderSecurityManager.exitSafeRegion(token); - } - } - - @Override - public ActionBarCallback getActionBarCallback() { - return new ActionBarHandler(mEditor); - } - - @Override - public boolean supports(int feature) { - return feature <= Features.LAST_CAPABILITY; - } -} |