1 /* 2 * Copyright (C) 2020 The Dagger Authors. 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 dagger.hilt.processor.internal.root; 18 19 import com.squareup.javapoet.JavaFile; 20 import java.io.IOException; 21 import java.io.Writer; 22 import java.util.regex.MatchResult; 23 import java.util.regex.Matcher; 24 import java.util.regex.Pattern; 25 import javax.annotation.processing.Filer; 26 import javax.lang.model.element.Element; 27 import javax.tools.JavaFileObject; 28 29 /** 30 * Typically we would just use {@code JavaFile#writeTo()} to write files. However, this formatter 31 * exists to add new lines inbetween interfaces. This can be important for classes with many 32 * interfaces (e.g. Dagger components) to avoid spamming the entire list of interfaces when 33 * reporting errors to the user. 34 * 35 * <p>See b/33108646. 36 */ 37 final class RootFileFormatter { 38 private static final Pattern CLASS_PATERN = Pattern.compile("(\\h*)(.*class.*implements)(.*\\{)"); 39 40 /** Formats the {@link JavaFile} java source file. */ write(JavaFile javaFile, Filer filer)41 static void write(JavaFile javaFile, Filer filer) throws IOException { 42 String fileName = 43 javaFile.packageName.isEmpty() 44 ? javaFile.typeSpec.name 45 : javaFile.packageName + "." + javaFile.typeSpec.name; 46 47 Element[] originatingElements = javaFile.typeSpec.originatingElements.toArray(new Element[0]); 48 49 StringBuilder sb = new StringBuilder(""); 50 javaFile.writeTo(sb); 51 String fileContent = formatInterfaces(sb.toString(), CLASS_PATERN); 52 53 JavaFileObject filerSourceFile = filer.createSourceFile(fileName, originatingElements); 54 try (Writer writer = filerSourceFile.openWriter()) { 55 writer.write(fileContent); 56 } catch (Exception e) { 57 try { 58 filerSourceFile.delete(); 59 } catch (Exception ignored) { 60 // Nothing to do. 61 } 62 throw e; 63 } 64 } 65 formatInterfaces(String content, Pattern pattern)66 private static String formatInterfaces(String content, Pattern pattern) { 67 Matcher matcher = pattern.matcher(content); 68 StringBuffer sb = new StringBuffer(content.length()); 69 while (matcher.find()) { 70 MatchResult result = matcher.toMatchResult(); 71 String spaces = result.group(1); 72 String prefix = result.group(2); 73 String interfaces = result.group(3); 74 String formattedInterfaces = formatInterfaces(spaces, interfaces); 75 matcher.appendReplacement( 76 sb, Matcher.quoteReplacement(spaces + prefix + formattedInterfaces)); 77 } 78 matcher.appendTail(sb); 79 return sb.toString(); 80 } 81 formatInterfaces(String prefixSpaces, String interfaces)82 private static String formatInterfaces(String prefixSpaces, String interfaces) { 83 StringBuilder sb = new StringBuilder(interfaces); 84 String newLine = String.format("\n%s ", prefixSpaces); 85 86 // Add a line break after each interface so that there's only 1 interface per line. 87 int i = 0; 88 int bracketCount = 0; 89 while (i >= 0 && i < sb.length()) { 90 char c = sb.charAt(i++); 91 if (c == '<') { 92 bracketCount++; 93 } else if (c == '>') { 94 bracketCount--; 95 } else if (c == ',' && bracketCount == 0) { 96 sb.insert(i++, newLine); 97 } 98 } 99 return sb.toString(); 100 } 101 RootFileFormatter()102 private RootFileFormatter() {} 103 } 104