1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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.layoutopt.uix.groovy; 18 19 import com.android.layoutopt.uix.LayoutAnalysis; 20 import com.android.layoutopt.uix.xml.XmlDocumentBuilder; 21 22 import java.util.Map; 23 import java.util.List; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 27 import groovy.lang.GString; 28 import groovy.xml.dom.DOMCategory; 29 import org.w3c.dom.Node; 30 import org.w3c.dom.NodeList; 31 import org.w3c.dom.Element; 32 33 /** 34 * Support class for Groovy rules. This class adds new Groovy capabilities 35 * to {@link com.android.layoutopt.uix.LayoutAnalysis} and {@link org.w3c.dom.Node}. 36 */ 37 public class LayoutAnalysisCategory { 38 private static final String ANDROID_PADDING = "android:padding"; 39 private static final String ANDROID_PADDING_LEFT = "android:paddingLeft"; 40 private static final String ANDROID_PADDING_TOP = "android:paddingTop"; 41 private static final String ANDROID_PADDING_RIGHT = "android:paddingRight"; 42 private static final String ANDROID_PADDING_BOTTOM = "android:paddingBottom"; 43 private static final String ANDROID_LAYOUT_WIDTH = "android:layout_width"; 44 private static final String ANDROID_LAYOUT_HEIGHT = "android:layout_height"; 45 private static final String VALUE_FILL_PARENT = "fill_parent"; 46 private static final String VALUE_WRAP_CONTENT = "wrap_content"; 47 48 private static final String[] sContainers = new String[] { 49 "FrameLayout", "LinearLayout", "RelativeLayout", "SlidingDrawer", 50 "AbsoluteLayout", "TableLayout", "Gallery", "GridView", "ListView", 51 "RadioGroup", "ScrollView", "HorizontalScrollView", "Spinner", 52 "ViewSwitcher", "ViewFlipper", "ViewAnimator", "ImageSwitcher", 53 "TextSwitcher", "android.gesture.GestureOverlayView", "TabHost" 54 }; 55 static { 56 Arrays.sort(sContainers); 57 } 58 59 /** 60 * xmlNode.isContainer() 61 * 62 * @return True if the specified node corresponds to a container widget. 63 */ isContainer(Element element)64 public static boolean isContainer(Element element) { 65 return Arrays.binarySearch(sContainers, element.getNodeName()) >= 0; 66 } 67 68 /** 69 * xmlNode.all() 70 * 71 * Same as xmlNode.'**' but excludes xmlNode from the results. 72 * 73 * @return All descendants, this node excluded. 74 */ all(Element element)75 public static List<Node> all(Element element) { 76 NodeList list = DOMCategory.depthFirst(element); 77 int count = list.getLength(); 78 List<Node> nodes = new ArrayList<Node>(count - 1); 79 for (int i = 1; i < count; i++) { 80 nodes.add(list.item(i)); 81 } 82 return nodes; 83 } 84 85 /** 86 * Returns the start line of this node. 87 * 88 * @return The start line or -1 if the line is unknown. 89 */ getStartLine(Node node)90 public static int getStartLine(Node node) { 91 final Object data = node == null ? null : 92 node.getUserData(XmlDocumentBuilder.NODE_START_LINE); 93 return data == null ? -1 : (Integer) data; 94 } 95 96 /** 97 * Returns the end line of this node. 98 * 99 * @return The end line or -1 if the line is unknown. 100 */ getEndLine(Node node)101 public static int getEndLine(Node node) { 102 final Object data = node == null ? null : 103 node.getUserData(XmlDocumentBuilder.NODE_END_LINE); 104 return data == null ? -1 : (Integer) data; 105 } 106 107 /** 108 * xmlNode.hasPadding() 109 * 110 * @return True if the node has one ore more padding attributes. 111 */ hasPadding(Element element)112 public static boolean hasPadding(Element element) { 113 return element.getAttribute(ANDROID_PADDING).length() > 0 || 114 element.getAttribute(ANDROID_PADDING_LEFT).length() > 0 || 115 element.getAttribute(ANDROID_PADDING_TOP).length() > 0 || 116 element.getAttribute(ANDROID_PADDING_BOTTOM).length() > 0 || 117 element.getAttribute(ANDROID_PADDING_RIGHT).length() > 0; 118 } 119 120 /** 121 * Returns whether this node's width is fill_parent. 122 */ isWidthFillParent(Element element)123 public static boolean isWidthFillParent(Element element) { 124 return element.getAttribute(ANDROID_LAYOUT_WIDTH).equals(VALUE_FILL_PARENT); 125 } 126 127 /** 128 * Returns whether this node's width is wrap_content. 129 */ isWidthWrapContent(Element element)130 public static boolean isWidthWrapContent(Element element) { 131 return element.getAttribute(ANDROID_LAYOUT_WIDTH).equals(VALUE_WRAP_CONTENT); 132 } 133 134 /** 135 * Returns whether this node's height is fill_parent. 136 */ isHeightFillParent(Element element)137 public static boolean isHeightFillParent(Element element) { 138 return element.getAttribute(ANDROID_LAYOUT_HEIGHT).equals(VALUE_FILL_PARENT); 139 } 140 141 /** 142 * Returns whether this node's height is wrap_content. 143 */ isHeightWrapContent(Element element)144 public static boolean isHeightWrapContent(Element element) { 145 return element.getAttribute(ANDROID_LAYOUT_HEIGHT).equals(VALUE_WRAP_CONTENT); 146 } 147 148 /** 149 * xmlNode.isRoot() 150 * 151 * @return True if xmlNode is the root of the document, false otherwise 152 */ isRoot(Node node)153 public static boolean isRoot(Node node) { 154 return node.getOwnerDocument().getDocumentElement() == node; 155 } 156 157 /** 158 * xmlNode.is("tagName") 159 * 160 * @return True if xmlNode.getNodeName().equals(name), false otherwise. 161 */ is(Node node, String name)162 public static boolean is(Node node, String name) { 163 return node.getNodeName().equals(name); 164 } 165 166 /** 167 * xmlNode.depth() 168 * 169 * @return The maximum depth of the node. 170 */ depth(Node node)171 public static int depth(Node node) { 172 int maxDepth = 0; 173 NodeList list = node.getChildNodes(); 174 int count = list.getLength(); 175 176 for (int i = 0; i < count; i++) { 177 maxDepth = Math.max(maxDepth, depth(list.item(i))); 178 } 179 180 return maxDepth + 1; 181 } 182 183 /** 184 * analysis << "The issue" 185 * 186 * @return The analysis itself to chain calls. 187 */ leftShift(LayoutAnalysis analysis, GString description)188 public static LayoutAnalysis leftShift(LayoutAnalysis analysis, GString description) { 189 analysis.addIssue(description.toString()); 190 return analysis; 191 } 192 193 /** 194 * analysis << "The issue" 195 * 196 * @return The analysis itself to chain calls. 197 */ leftShift(LayoutAnalysis analysis, String description)198 public static LayoutAnalysis leftShift(LayoutAnalysis analysis, String description) { 199 analysis.addIssue(description); 200 return analysis; 201 } 202 203 /** 204 * analysis << [node: node, description: "The issue"] 205 * 206 * @return The analysis itself to chain calls. 207 */ leftShift(LayoutAnalysis analysis, Map issue)208 public static LayoutAnalysis leftShift(LayoutAnalysis analysis, Map issue) { 209 analysis.addIssue((Node) issue.get("node"), issue.get("description").toString()); 210 return analysis; 211 } 212 } 213