1 /* 2 * Copyright (C) 2012 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.idegen; 18 19 import com.google.common.base.Objects; 20 import com.google.common.base.Preconditions; 21 import com.google.common.base.Splitter; 22 import com.google.common.base.Strings; 23 import com.google.common.collect.Lists; 24 import com.google.common.collect.Maps; 25 import com.google.common.io.Files; 26 import com.google.common.io.LineProcessor; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.nio.charset.Charset; 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.logging.Logger; 35 36 /** 37 * Parses the make files and finds the appropriate section a given module. 38 */ 39 public class MakeFileParser { 40 MakeFileParser.class.getNamenull41 private static final Logger logger = Logger.getLogger(MakeFileParser.class.getName()); 42 public static final String VALUE_DELIMITER = "|"; 43 44 private enum State { 45 NEW, CONTINUE 46 } 47 private enum ModuleNameKey { 48 LOCAL_PACKAGE_NAME, 49 LOCAL_MODULE 50 }; 51 52 private File makeFile; 53 private String moduleName; 54 private HashMap<String, String> values; 55 56 /** 57 * Create a parser for a given make file and module name. 58 * <p> 59 * A make file may contain multiple modules. 60 * 61 * @param makeFile The make file to parse. 62 * @param moduleName The module to extract. 63 */ 64 public MakeFileParser(File makeFile, String moduleName) { 65 this.makeFile = Preconditions.checkNotNull(makeFile); 66 this.moduleName = Preconditions.checkNotNull(moduleName); 67 } 68 69 public Iterable<String> getValues(String key) { 70 String str = values.get(key); 71 if (str == null) { 72 return null; 73 } 74 return Splitter.on(VALUE_DELIMITER) .trimResultsnull75 .trimResults() 76 .omitEmptyStrings() 77 .split(str); 78 } 79 80 /** 81 * Extracts the relevant portion of the make file and converts into key value pairs. 82 * <p> 83 * Since each make file may contain multiple build targets (modules), this method will determine 84 * which part is the correct portion for the given module name. 85 * 86 * @throws IOException 87 */ parsenull88 public void parse() throws IOException { 89 values = Maps.newHashMap(); 90 logger.info("Parsing " + makeFile.getAbsolutePath() + " for module " + moduleName); 91 92 Files.readLines(makeFile, Charset.forName("UTF-8"), new LineProcessor<Object>() { 93 94 private String key; 95 96 private State state = State.NEW; 97 98 @Override 99 public boolean processLine(String line) throws IOException { 100 String trimmed = line.trim(); 101 if (Strings.isNullOrEmpty(trimmed)) { 102 state = State.NEW; 103 return true; 104 } 105 if (trimmed.equals("include $(CLEAR_VARS)")) { 106 // See if we are in the right module. 107 if (moduleName.equals(getModuleName())) { 108 return false; 109 } else { 110 values.clear(); 111 } 112 } else { 113 switch (state) { 114 case NEW: 115 trimmed = checkContinue(trimmed); 116 if (trimmed.contains("=")) { 117 String[] arr; 118 if (trimmed.contains(":")) { 119 arr = trimmed.split(":="); 120 } else { 121 arr = trimmed.split("\\+="); 122 } 123 if (arr.length > 2) { 124 logger.info("Malformed line " + line); 125 } else { 126 // Store the key in case the line continues 127 this.key = arr[0].trim(); 128 if (arr.length == 2) { 129 // There may be multiple values on one line. 130 List<String> valuesArr = tokenizeValue(arr[1].trim()); 131 for (String value : valuesArr) { 132 appendValue(this.key, value); 133 } 134 135 } 136 } 137 } else { 138 //logger.info("Skipping line " + line); 139 } 140 break; 141 case CONTINUE: 142 // append 143 trimmed = checkContinue(trimmed); 144 appendValue(key, trimmed); 145 break; 146 default: 147 148 } 149 } 150 return true; 151 } 152 153 private List<String> tokenizeValue(String value) { 154 // Value may contain function calls such as "$(call all-java-files-under)". 155 // Tokens are separated by spaces unless it's between parens. 156 StringBuilder token = new StringBuilder(); 157 ArrayList<String> tokens = Lists.newArrayList(); 158 int parenCount = 0; 159 for (int i = 0; i < value.length(); i++) { 160 char ch = value.charAt(i); 161 if (parenCount == 0 && ch == ' ') { 162 // Not in a paren and delimiter encountered. 163 // end token 164 if (token.length() > 0) { 165 tokens.add(token.toString()); 166 token = new StringBuilder(); 167 } 168 } else { 169 token.append(ch); 170 } 171 if (ch == '(') { 172 parenCount++; 173 } else if (ch == ')') { 174 parenCount--; 175 } 176 } 177 // end of line check 178 if (token.length() > 0) { 179 tokens.add(token.toString()); 180 } 181 return tokens; 182 } 183 184 private String getModuleName() { 185 for (ModuleNameKey key : ModuleNameKey.values()) { 186 String name = values.get(key.name()); 187 if (name != null) { 188 return name; 189 } 190 } 191 return null; 192 } 193 194 @Override 195 public Object getResult() { 196 return null; 197 } 198 199 private String checkContinue(String value) { 200 // Check for continuation character 201 if (value.charAt(value.length() - 1) == '\\') { 202 state = State.CONTINUE; 203 return value.substring(0, value.length() - 1); 204 } 205 state = State.NEW; 206 return value; 207 } 208 209 /** 210 * Add a value to the hash map. If the key already exists, will append instead of 211 * over-writing the existing value. 212 * 213 * @param key The hashmap key 214 * @param newValue The value to append. 215 */ 216 private void appendValue(String key, String newValue) { 217 String value = values.get(key); 218 if (value == null) { 219 values.put(key, newValue); 220 } else { 221 values.put(key, value + VALUE_DELIMITER + newValue); 222 } 223 } 224 }); 225 } 226 227 @Override toStringnull228 public String toString() { 229 return Objects.toStringHelper(this) 230 .add("values", values) 231 .toString(); 232 } 233 } 234