| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.intellij.android.designer.model; |
| |
| import com.android.ide.common.rendering.LayoutLibrary; |
| import com.android.ide.common.rendering.api.ViewInfo; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.tools.idea.rendering.RenderResult; |
| import com.android.tools.idea.rendering.RenderTask; |
| import com.intellij.android.designer.propertyTable.*; |
| import com.intellij.designer.model.*; |
| import com.intellij.designer.propertyTable.PropertyTable; |
| import com.intellij.openapi.module.Module; |
| import org.jetbrains.android.dom.attrs.AttributeDefinition; |
| import org.jetbrains.android.dom.attrs.AttributeDefinitions; |
| import org.jetbrains.android.dom.attrs.AttributeFormat; |
| import org.jetbrains.android.dom.attrs.StyleableDefinition; |
| import org.jetbrains.android.sdk.AndroidPlatform; |
| import org.jetbrains.android.sdk.AndroidTargetData; |
| import org.jetbrains.android.uipreview.ModuleClassLoader; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author Alexander Lobas |
| */ |
| @SuppressWarnings("unchecked") |
| public class PropertyParser { |
| public static final String KEY = "PROPERTY_PARSER"; |
| |
| private static final String[] DEFAULT_LAYOUT_PARAMS = {"ViewGroup_Layout"}; |
| private static final String LAYOUT_PREFIX = "layout_"; |
| private static final String LAYOUT_MARGIN_PREFIX = "layout_margin"; |
| |
| private MetaManager myMetaManager; |
| private AttributeDefinitions myDefinitions; |
| private ModuleClassLoader myClassLoader; |
| private Map<String, List<Property>> myCachedProperties; |
| |
| public PropertyParser(@NotNull RenderResult result) { |
| assert result.getSession() != null; |
| assert result.getSession().getResult().isSuccess(); |
| RenderTask renderTask = result.getRenderTask(); |
| assert renderTask != null; |
| IAndroidTarget target = renderTask.getConfiguration().getTarget(); |
| assert target != null; |
| Module module = renderTask.getModule(); |
| |
| myMetaManager = ViewsMetaManager.getInstance(module.getProject()); |
| myCachedProperties = myMetaManager.getCache(target.hashString()); |
| if (myCachedProperties == null) { |
| myMetaManager.setCache(target.hashString(), myCachedProperties = new HashMap<String, List<Property>>()); |
| } |
| |
| AndroidPlatform androidPlatform = AndroidPlatform.getInstance(module); |
| assert androidPlatform != null; |
| AndroidTargetData targetData = androidPlatform.getSdkData().getTargetData(target); |
| myDefinitions = targetData.getPublicAttrDefs(module.getProject()); |
| |
| LayoutLibrary library = renderTask.getLayoutLib(); |
| myClassLoader = ModuleClassLoader.get(library, module); |
| } |
| |
| public void load(RadViewComponent component) throws Exception { |
| MetaModel model = component.getMetaModelForProperties(); |
| String target = model.getTarget(); |
| if (target == null) { |
| ViewInfo info = component.getViewInfo(); |
| if (info == null) { |
| component.setProperties(Collections.<Property>emptyList()); |
| } |
| else { |
| Class<?> componentClass = configureClass(myClassLoader.loadClass(info.getClassName())); |
| component.setProperties(loadWidgetProperties(componentClass, model)); |
| } |
| } |
| else { |
| component.setProperties(loadWidgetProperties(myClassLoader.loadClass(target), model)); |
| } |
| |
| RadComponent parent = component.getParent(); |
| if (parent != null) { |
| String[] layoutParams = null; |
| RadLayout layout = parent.getLayout(); |
| |
| if (layout instanceof RadViewLayoutWithData) { |
| layoutParams = ((RadViewLayoutWithData)layout).getLayoutParams(); |
| } |
| else if (parent == parent.getRoot()) { |
| layoutParams = DEFAULT_LAYOUT_PARAMS; |
| } |
| |
| if (layoutParams != null) { |
| MetaModel[] models = new MetaModel[layoutParams.length]; |
| models[0] = parent.getMetaModelForProperties(); |
| |
| for (int i = 1; i < layoutParams.length; i++) { |
| if (models[i - 1] == null) { |
| break; |
| } |
| String extendTarget = models[i - 1].getTarget(); |
| if (extendTarget == null) { |
| break; |
| } |
| |
| Class<?> superClass = myClassLoader.loadClass(extendTarget).getSuperclass(); |
| if (superClass != null) { |
| superClass = configureClass(superClass); |
| models[i] = myMetaManager.getModelByTarget(superClass.getName()); |
| } |
| } |
| |
| List<Property> properties = loadLayoutProperties(layoutParams, 0, models); |
| if (!properties.isEmpty()) { |
| properties = new ArrayList<Property>(properties); |
| properties.addAll(component.getProperties()); |
| component.setProperties(properties); |
| } |
| } |
| } |
| } |
| |
| private List<Property> loadWidgetProperties(Class<?> componentClass, @Nullable MetaModel model) throws Exception { |
| String component = componentClass.getSimpleName(); |
| |
| List<Property> properties = myCachedProperties.get(component); |
| |
| if (properties == null) { |
| properties = new ArrayList<Property>(); |
| myCachedProperties.put(component, properties); |
| |
| if ("View".equals(component)) { |
| properties.add(new StyleProperty()); |
| } |
| |
| StyleableDefinition definitions = myDefinitions.getStyleableByName(component); |
| if (definitions != null) { |
| boolean padding = false; |
| |
| for (AttributeDefinition definition : definitions.getAttributes()) { |
| String name = definition.getName(); |
| Set<AttributeFormat> formats = definition.getFormats(); |
| Property property; |
| |
| if ("padding".equals(name) && "View".equals(component)) { |
| padding = true; |
| } |
| if (formats.contains(AttributeFormat.Flag)) { |
| property = new FlagProperty(name, definition); |
| } |
| else { |
| if ("id".equals(name) && "View".equals(component)) { |
| property = new IdProperty(name, definition); |
| } |
| else { |
| property = new AttributeProperty(name, definition); |
| } |
| } |
| |
| if (model != null) { |
| model.decorate(property, name); |
| } |
| properties.add(property); |
| } |
| |
| if (padding) { |
| CompoundDimensionProperty paddingProperty = new CompoundDimensionProperty("padding"); |
| moveProperties(properties, paddingProperty, |
| "padding", "all", |
| "paddingLeft", "left", |
| "paddingTop", "top", |
| "paddingRight", "right", |
| "paddingBottom", "bottom"); |
| if (model != null) { |
| paddingProperty.decorate(model); |
| } |
| properties.add(paddingProperty); |
| } |
| } |
| |
| Class<?> superComponentClass = componentClass.getSuperclass(); |
| if (superComponentClass != null) { |
| superComponentClass = configureClass(superComponentClass); |
| MetaModel superModel = myMetaManager.getModelByTarget(superComponentClass.getName()); |
| |
| if (model != null && superModel != null && model.getInplaceProperties().isEmpty()) { |
| model.setInplaceProperties(superModel.getInplaceProperties()); |
| } |
| |
| List<Property> superProperties = loadWidgetProperties(superComponentClass, superModel); |
| for (Property superProperty : superProperties) { |
| if (PropertyTable.findProperty(properties, superProperty) == -1) { |
| if (model == null) { |
| properties.add(superProperty); |
| } |
| else { |
| properties.add(model.decorateWithOverride(superProperty)); |
| } |
| } |
| } |
| } |
| |
| if (!properties.isEmpty()) { |
| Collections.sort(properties, new Comparator<Property>() { |
| @Override |
| public int compare(Property p1, Property p2) { |
| return p1.getName().compareTo(p2.getName()); |
| } |
| }); |
| |
| if (model != null) { |
| for (String topName : model.getTopProperties()) { |
| PropertyTable.moveProperty(properties, topName, properties, 0); |
| } |
| } |
| |
| PropertyTable.moveProperty(properties, "style", properties, 0); |
| } |
| } |
| |
| return properties; |
| } |
| |
| private Class<?> configureClass(Class<?> viewClass) throws Exception { |
| if (viewClass.getName().equals("com.android.layoutlib.bridge.MockView")) { |
| return myClassLoader.loadClass("android.view.View"); |
| } |
| return viewClass; |
| } |
| |
| private List<Property> loadLayoutProperties(String[] components, int index, MetaModel[] models) throws Exception { |
| String component = components[index]; |
| MetaModel model = models[index]; |
| |
| List<Property> properties = myCachedProperties.get(component); |
| |
| if (properties == null) { |
| properties = new ArrayList<Property>(); |
| myCachedProperties.put(component, properties); |
| |
| StyleableDefinition definitions = myDefinitions.getStyleableByName(component); |
| if (definitions != null) { |
| boolean margin = false; |
| |
| for (AttributeDefinition definition : definitions.getAttributes()) { |
| String name = definition.getName(); |
| boolean important = true; |
| Set<AttributeFormat> formats = definition.getFormats(); |
| Property property; |
| |
| if (name.startsWith(LAYOUT_MARGIN_PREFIX)) { |
| name = name.substring(LAYOUT_PREFIX.length()); |
| important = false; |
| } |
| else if (name.startsWith(LAYOUT_PREFIX)) { |
| name = "layout:" + name.substring(LAYOUT_PREFIX.length()); |
| } |
| |
| if ("margin".equals(name) && "ViewGroup_MarginLayout".equals(component)) { |
| margin = true; |
| } |
| if ("layout:width".equals(name) || "layout:height".equals(name)) { |
| property = new AttributePropertyWithDefault(name, definition, "wrap_content"); |
| } |
| else if (formats.contains(AttributeFormat.Flag)) { |
| if ("layout:gravity".equals(name)) { |
| property = new GravityProperty(name, definition); |
| } |
| else { |
| property = new FlagProperty(name, definition); |
| } |
| } |
| else { |
| property = new AttributeProperty(name, definition); |
| } |
| |
| if (model != null) { |
| model.decorate(property, name); |
| } |
| property.setImportant(important); |
| properties.add(property); |
| } |
| |
| if (margin) { |
| CompoundDimensionProperty marginProperty = new CompoundDimensionProperty("layout:margin"); |
| moveProperties(properties, marginProperty, |
| "margin", "all", |
| "marginLeft", "left", |
| "marginTop", "top", |
| "marginRight", "right", |
| "marginBottom", "bottom", |
| "marginStart", "start", |
| "marginEnd", "end"); |
| if (model != null) { |
| marginProperty.decorate(model); |
| } |
| marginProperty.setImportant(true); |
| properties.add(marginProperty); |
| } |
| } |
| |
| if (++index < components.length) { |
| for (Property property : loadLayoutProperties(components, index, models)) { |
| if (PropertyTable.findProperty(properties, property) == -1) { |
| if (model == null) { |
| properties.add(property); |
| } |
| else { |
| property = model.decorateWithOverride(property); |
| properties.add(property); |
| } |
| } |
| } |
| } |
| |
| if (!properties.isEmpty()) { |
| Collections.sort(properties, new Comparator<Property>() { |
| @Override |
| public int compare(Property p1, Property p2) { |
| return p1.getName().compareTo(p2.getName()); |
| } |
| }); |
| |
| PropertyTable.moveProperty(properties, "layout:margin", properties, 0); |
| PropertyTable.moveProperty(properties, "layout:gravity", properties, 0); |
| PropertyTable.moveProperty(properties, "layout:height", properties, 0); |
| PropertyTable.moveProperty(properties, "layout:width", properties, 0); |
| } |
| |
| if (model != null) { |
| Class<RadLayout> layout = model.getLayout(); |
| if (layout != null) { |
| layout.newInstance().configureProperties(properties); |
| } |
| } |
| } |
| |
| return properties; |
| } |
| |
| public static void moveProperties(List<Property> source, Property destination, String... names) { |
| List<Property> children = destination.getChildren(null); |
| for (int i = 0; i < names.length; i += 2) { |
| Property property = PropertyTable.extractProperty(source, names[i]); |
| if (property != null) { |
| children.add(property.createForNewPresentation(destination, names[i + 1])); |
| } |
| } |
| } |
| |
| public boolean isAssignableFrom(MetaModel base, MetaModel test) { |
| try { |
| Class<?> baseClass = myClassLoader.loadClass(base.getTarget()); |
| Class<?> testClass = myClassLoader.loadClass(test.getTarget()); |
| return baseClass.isAssignableFrom(testClass); |
| } |
| catch (Throwable ignored) { |
| } |
| return false; |
| } |
| } |