• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 import com.github.javaparser.utils.Log;
2 
3 import java.io.BufferedReader;
4 import java.io.Closeable;
5 import java.io.File;
6 import java.io.FileOutputStream;
7 import java.io.FileReader;
8 import java.io.IOException;
9 import java.io.PrintStream;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Set;
14 import java.util.StringJoiner;
15 import java.util.logging.Logger;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
18 
19 public class StatsGenerator {
20     private static final Logger LOGGER = Logger.getLogger(StatsGenerator.class.toString());
21     private static final boolean DEBUG = false;
22     private static final Pattern SINGLE_ENTRY = Pattern.compile(
23             "^\\s*((optional|repeated) )?(([a-zA-Z_0-9\\.]+)\\s+)?([a-zA-Z_0-9]+)\\s*=\\s*(\\-?\\d+)([^\\d;][^\\;]*)?;$");
24 
25     private final List<String> mAllImports = new ArrayList<>();
26     private final File mRootPath;
27 
StatsGenerator(File rootPath)28     public StatsGenerator(File rootPath) {
29         mRootPath = rootPath;
30     }
31 
process(File atomFile, Set<File> atomExtensions, String module, String packageName, File outputFile)32     public void process(File atomFile, Set<File> atomExtensions, String module, String packageName,
33             File outputFile) throws IOException {
34         PrintStream output = new PrintStream(new FileOutputStream(outputFile));
35 
36         output.println("package " + packageName + ";");
37         output.println();
38         output.println("import android.util.StatsEvent;");
39         output.println();
40 
41         String className = outputFile.getName();
42         className = className.substring(0, className.indexOf("."));
43         output.println("public class " + className + " { ");
44         output.println();
45 
46         GroupEntry out = new GroupEntry(null);
47         parseFile(out, atomFile);
48 
49         if (atomExtensions != null) {
50             for (File ext : atomExtensions) {
51                 parseExtension(out, ext);
52             }
53         }
54 
55         GroupEntry atom = out.findGroup("atom");
56         GroupEntry pulledGroup = atom.findGroup("pulled");
57         List<SingleEntry> children = new ArrayList<>();
58         children.addAll(atom.findGroup("pushed").getSingles());
59         children.addAll(atom.findGroup("pulled").getSingles());
60 
61         for (SingleEntry e : atom.getSingles()) {
62             if (e.extra.contains(module)) {
63                 e.writeTo("", output);
64                 output.println();
65 
66                 System.out.println(">> " + out.findGroup(e.type) + "  " + e.type);
67                 printGroup(out.findGroup(e.type), output, convertToSymbolGroupPrefix(e.type));
68                 output.println();
69                 output.println();
70             }
71         }
72 
73         for (SingleEntry e : children) {
74             if (e.extra.contains(module)) {
75                 e.writeTo("", output);
76                 output.println();
77 
78                 if (DEBUG) System.out.println(">> " + out.findGroup(e.type) + "  " + e.type);
79                 printGroup(out.findGroup(e.type), output, convertToSymbolGroupPrefix(e.type));
80                 output.println();
81                 output.println();
82             }
83         }
84 
85         for (SingleEntry e : pulledGroup.getSingles()) {
86             if (e.extra.contains(module)) {
87                 GroupEntry group = out.findGroup(e.type);
88                 output.println(group.constructBuildStatsEventMethod());
89             }
90         }
91 
92         // Add a Placeholder write method
93         output.println("  // Placeholder code for local development only");
94         output.println("  public static void write(int code, Object... params) { }");
95         output.println();
96         output.println("}");
97         output.close();
98     }
99 
printGroup(GroupEntry entry, PrintStream output, String prefix)100     private static void printGroup(GroupEntry entry, PrintStream output, String prefix) {
101         for (SingleEntry e : entry.getSingles()) {
102             GroupEntry subGroup = entry.findGroup(e.type);
103             if (subGroup != null) {
104                 printGroup(subGroup, output, prefix + convertToSymbolGroupPrefix(e.name));
105             } else {
106                 switch (e.type) {
107                     case "bool":
108                     case "int32":
109                     case "int64":
110                     case "float":
111                     case "string":
112                     case "null":    // In case of enum
113                         e.writeTo(prefix, output);
114                         break;
115                     default:
116                         LOGGER.warning("Type not found " + e);
117                 }
118             }
119         }
120     }
121 
convertToSymbolGroupPrefix(String name)122     private static String convertToSymbolGroupPrefix(String name) {
123         int dot = name.lastIndexOf('.');
124         if (dot >= 0) {
125             name = name.substring(dot + 1);
126         }
127         return name.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase() + "__";
128     }
129 
parseFile(GroupEntry out, File path)130     private String parseFile(GroupEntry out, File path) throws IOException {
131         ArrayList<String> outImports = new ArrayList<>();
132         String outerPath;
133         try (MyReader reader = new MyReader(new BufferedReader(new FileReader(path)), outImports)) {
134             parseGroup(out, reader, "");
135             out.javaPackage = reader.javaPackage;
136             outerPath = reader.rootPrefix;
137         }
138         parseImports(outImports, out, false);
139         return outerPath;
140     }
141 
parseImports(ArrayList<String> imports, GroupEntry out, boolean skipDuplicate)142     private void parseImports(ArrayList<String> imports, GroupEntry out,
143             boolean skipDuplicate) throws IOException {
144         for (String p : imports) {
145             if (mAllImports.contains(p) && skipDuplicate) {
146                 System.err.println("Importing already parsed file " + p);
147                 continue;
148             }
149             mAllImports.add(p);
150             File importFile = new File(mRootPath, p);
151             if (importFile.exists()) {
152                 GroupEntry grp = new GroupEntry(null);
153                 String pkg = parseFile(grp, importFile);
154 
155                 GroupEntry grp2 = out.imports.get(pkg);
156                 if (grp2 == null) {
157                     out.imports.put(pkg, grp);
158                 } else {
159                     grp2.children.addAll(grp.children);
160                     grp2.imports.putAll(grp.imports);
161                 }
162             }
163         }
164     }
165 
parseExtension(GroupEntry out, File path)166     private void parseExtension(GroupEntry out, File path) throws IOException {
167         ArrayList<String> outImports = new ArrayList<>();
168         try (MyReader reader = new MyReader(new BufferedReader(new FileReader(path)), outImports)) {
169             String line = null;
170             try {
171                 while (!(line = reader.getEntry()).startsWith("}")) {
172                     if (line.endsWith("{")) {
173                         String prefix = "";
174                         if (DEBUG) System.out.println(prefix + " :: " + line);
175                         String[] parts = line.split(" ", 3);
176 
177                         GroupEntry group = new GroupEntry(out.root);
178                         group.name = parts[1];
179                         group.type = parts[0];
180 
181                         GroupEntry existing = out.findGroup(group.name);
182                         if (existing != null) {
183                             if (!"extend".equals(group.type)) {
184                                 System.out.println("Found duplicated entry without extension");
185                                 continue;
186                             }
187                             parseGroup(existing, reader, prefix + "   ");
188                         } else {
189                             parseGroup(group, reader, prefix + "   ");
190                             out.children.add(group);
191                         }
192                     }
193                 }
194             } catch (RuntimeException e) {
195                 LOGGER.warning("Error at line " + line);
196                 throw e;
197             }
198 
199             parseGroup(out, reader, "");
200         }
201 
202         parseImports(outImports, out, true);
203     }
204 
parseGroup(GroupEntry out, MyReader reader, String prefix)205     private static void parseGroup(GroupEntry out, MyReader reader, String prefix)
206             throws IOException {
207         String line = null;
208         try {
209             while (!(line = reader.getEntry()).startsWith("}")) {
210                 Entry entry;
211                 if (line.endsWith("{")) {
212                     if (DEBUG) System.out.println(prefix + " :: " + line);
213                     String[] parts = line.split(" ", 3);
214 
215                     GroupEntry group = new GroupEntry(out.root);
216                     group.name = parts[1];
217                     group.type = parts[0];
218 
219                     parseGroup(group, reader, prefix + "   ");
220                     entry = group;
221                 } else {
222                     String ot = line;
223                     Matcher m = SINGLE_ENTRY.matcher(line.trim());
224                     if (!m.matches()) {
225                         continue;
226                     }
227                     SingleEntry singleEntry = new SingleEntry();
228                     singleEntry.type = m.group(4) + "";
229                     singleEntry.name = m.group(5);
230                     singleEntry.value = m.group(6);
231                     singleEntry.extra = m.group(7) + "";
232                     entry = singleEntry;
233                     if (DEBUG) System.out.println(prefix + " -- " + line);
234                 }
235 
236                 out.children.add(entry);
237             }
238         } catch (RuntimeException e) {
239             LOGGER.warning("Error at line " + line);
240             throw e;
241         }
242     }
243 
244     private static class Entry {
245         String type;
246         String name;
247 
javaType()248         public String javaType() {
249             switch (type) {
250                 case "bool":
251                     return "boolean";
252                 case "int32":
253                     return "int";
254                 case "int64":
255                     return "long";
256                 case "float":
257                     return "float";
258                 case "string":
259                     return "String";
260                 default:
261                     return "Object";
262             }
263         }
264 
265         /**
266          * Convert {@code name} from lower_underscore_case to lowerCamelCase.
267          *
268          * The equivalent in guava would be {@code LOWER_UNDERSCORE.to(LOWER_CAMEL, name)}, but to
269          * keep the build system simple we don't want to depend on guava.
270          */
javaName()271         public String javaName() {
272             if (name.length() == 0) {
273                 return "";
274             }
275             StringBuilder sb = new StringBuilder(name.length());
276             sb.append(name.charAt(0));
277             boolean upperCaseNext = false;
278             for (int i = 1; i < name.length(); i++) {
279                 char c = name.charAt(i);
280                 if (c == '_') {
281                     upperCaseNext = true;
282                 } else {
283                     if (upperCaseNext) {
284                         c = Character.toUpperCase(c);
285                     }
286                     sb.append(c);
287                     upperCaseNext = false;
288                 }
289             }
290             return sb.toString();
291         }
292 
293         @Override
toString()294         public String toString() {
295             return name + ":" + type;
296         }
297     }
298 
299     private static class SingleEntry extends Entry {
300         String value;
301         String extra;
302 
writeTo(String prefix, PrintStream output)303         public void writeTo(String prefix, PrintStream output) {
304             output.println("  public static final int "
305                     + prefix + name.toUpperCase() + " = " + value + ";");
306         }
307 
constructStatsEventWriter(String builderName, GroupEntry g)308         public String constructStatsEventWriter(String builderName, GroupEntry g) {
309             switch (type) {
310                 case "bool":
311                     return String.format("%s.writeBoolean(%s);", builderName, javaName());
312                 case "int32":
313                     return String.format("%s.writeInt(%s);", builderName, javaName());
314                 case "int64":
315                     return String.format("%s.writeLong(%s);", builderName, javaName());
316                 case "float":
317                     return String.format("%s.writeFloat(%s);", builderName, javaName());
318                 case "string":
319                     return String.format("%s.writeString(%s);", builderName, javaName());
320                 default:
321                     LOGGER.warning("Type not found " + type + "  " + g.name);
322                     return ";";
323             }
324         }
325     }
326 
327     private static class GroupEntry extends Entry {
328         final HashMap<String, GroupEntry> imports;
329         final GroupEntry root;
330         final ArrayList<Entry> children = new ArrayList<>();
331 
332         String javaPackage = "";
333 
GroupEntry(GroupEntry root)334         public GroupEntry(GroupEntry root) {
335             if (root == null) {
336                 this.root = this;
337                 this.imports = new HashMap<>();
338             } else {
339                 this.root = root;
340                 this.imports = root.imports;
341             }
342         }
343 
findGroup(String name)344         public GroupEntry findGroup(String name) {
345             for (Entry e : children) {
346                 if (e.name.equalsIgnoreCase(name)) {
347                     return (GroupEntry) e;
348                 }
349             }
350             if (root != this) {
351                 GroupEntry e = root.findGroup(name);
352                 if (e != null) {
353                     return e;
354                 }
355             }
356             if (name.indexOf(".") >= 0) {
357                 // Look in imports
358                 String pkg = name.substring(0, name.lastIndexOf(".") + 1);
359                 String key = name.substring(name.lastIndexOf(".") + 1);
360 
361                 GroupEntry imp = imports.get(pkg);
362                 if (imp != null) {
363                     return imp.findGroup(key);
364                 }
365                 // Try import with a subclass packageName
366                 if (javaPackage != null) {
367                     imp = imports.get(javaPackage + pkg);
368                     if (imp != null) {
369                         return imp.findGroup(key);
370                     }
371                 }
372             }
373             return null;
374         }
375 
getSingles()376         public List<SingleEntry> getSingles() {
377             List<SingleEntry> result = new ArrayList<>();
378             for (Entry e : children) {
379                 if (e instanceof SingleEntry) {
380                     result.add((SingleEntry) e);
381                 }
382             }
383             return result;
384         }
385 
constructBuildStatsEventMethod()386         public String constructBuildStatsEventMethod() {
387             StringJoiner responseBuilder = new StringJoiner("\n");
388             responseBuilder.add("  // Placeholder code for local development only");
389             StringJoiner argBuilder = new StringJoiner(", ");
390             getSingles().forEach(entry -> argBuilder.add(
391                     entry.javaType() + " " + entry.javaName()));
392 
393 
394             String signature = String.format(
395                     "  public static StatsEvent buildStatsEvent(int code, %s){", argBuilder);
396 
397             responseBuilder.add(signature)
398                     .add("      final StatsEvent.Builder builder = StatsEvent.newBuilder();")
399                     .add("      builder.setAtomId(code);");
400             getSingles().stream().map(
401                     entry -> entry.constructStatsEventWriter("      builder",this)).forEach(
402                     responseBuilder::add);
403 
404             return responseBuilder.add("      return builder.build();")
405                     .add("  }").toString();
406         }
407     }
408 
409     private static class MyReader implements Closeable {
410 
411         final List<String> outImports;
412         final BufferedReader reader;
413 
414         String rootPrefix = "";
415         String javaPackage = "";
416         String javaOuterClassName = "";
417         boolean javaMultipleFiles = false;
418 
419         boolean started = false;
420         boolean finished = false;
421 
MyReader(BufferedReader reader, List<String> outImport)422         MyReader(BufferedReader reader, List<String> outImport) {
423             this.reader = reader;
424             this.outImports = outImport;
425         }
426 
extractQuotes(String line)427         private String extractQuotes(String line) {
428             Pattern p = Pattern.compile("\"([^\"]*)\"");
429             Matcher m = p.matcher(line);
430             return m.find() ? m.group(1) : "";
431         }
432 
parseHeaders()433         private String parseHeaders() throws IOException {
434             String line = getEntry();
435             if (line.startsWith("message") || line.equals("}")
436                     || line.startsWith("enum") || line.startsWith("extend")) {
437                 return line;
438             }
439             if (line.startsWith("import")) {
440                 String impSrc = extractQuotes(line);
441                 if (!impSrc.isEmpty()) {
442                     outImports.add(impSrc);
443                 }
444             } else if (line.startsWith("option")) {
445                 if (line.contains(" java_package ")) {
446                     rootPrefix = extractQuotes(line) + ".";
447                     javaPackage = rootPrefix;
448                 } else if (line.contains(" java_outer_classname ")) {
449                     javaOuterClassName = extractQuotes(line);
450                 } else if (line.contains(" java_multiple_files ")) {
451                     javaMultipleFiles = line.contains("true");
452                 }
453             } else if (line.startsWith("package")) {
454                 rootPrefix = line.split(" ")[1].split(";")[0].trim() + ".";
455                 javaPackage = rootPrefix;
456             }
457             return parseHeaders();
458         }
459 
onHeaderParseComplete()460         private void onHeaderParseComplete() {
461             if (!javaMultipleFiles && !javaOuterClassName.isEmpty()) {
462                 rootPrefix = rootPrefix + javaOuterClassName + ".";
463             }
464         }
465 
getEntry()466         String getEntry() throws IOException {
467             if (!started) {
468                 started = true;
469                 String entry = parseHeaders();
470                 onHeaderParseComplete();
471                 return entry;
472             }
473             String line = reader.readLine();
474 
475             if (line == null) {
476                 // Finished everything
477                 finished = true;
478                 return "}";
479             }
480 
481             line = line.trim();
482 
483             // Skip comments
484             int commentIndex = line.indexOf("//");
485             if (commentIndex > -1) {
486                 line = line.substring(0, commentIndex).trim();
487             }
488 
489             if (line.startsWith("/*")) {
490                 while (!line.contains("*/")) line = reader.readLine().trim();
491                 line = getEntry();
492             }
493 
494             if (!line.endsWith("{") && !line.endsWith(";") && !line.endsWith("}")) {
495                 line = line + " " + getEntry();
496             }
497             return line.trim();
498         }
499 
500         @Override
close()501         public void close() throws IOException {
502             reader.close();
503         }
504     }
505 }
506