• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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