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