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 static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 20 import static java.nio.charset.StandardCharsets.UTF_8; 21 22 import androidx.room.compiler.processing.XElement; 23 import androidx.room.compiler.processing.XFiler.Mode; 24 import androidx.room.compiler.processing.XProcessingEnv; 25 import androidx.room.compiler.processing.compat.XConverters; 26 import com.google.common.collect.ImmutableList; 27 import com.squareup.javapoet.JavaFile; 28 import java.io.BufferedWriter; 29 import java.io.IOException; 30 import java.io.OutputStream; 31 import java.io.OutputStreamWriter; 32 import java.io.Writer; 33 import java.util.regex.MatchResult; 34 import java.util.regex.Matcher; 35 import java.util.regex.Pattern; 36 37 /** 38 * Typically we would just use {@code JavaFile#writeTo()} to write files. However, this formatter 39 * exists to add new lines inbetween interfaces. This can be important for classes with many 40 * interfaces (e.g. Dagger components) to avoid spamming the entire list of interfaces when 41 * reporting errors to the user. 42 * 43 * <p>See b/33108646. 44 */ 45 final class RootFileFormatter { 46 private static final Pattern CLASS_PATERN = Pattern.compile("(\\h*)(.*class.*implements)(.*\\{)"); 47 48 /** Formats the {@link JavaFile} java source file. */ write(JavaFile javaFile, XProcessingEnv env)49 static void write(JavaFile javaFile, XProcessingEnv env) throws IOException { 50 ImmutableList<XElement> originatingElements = 51 javaFile.typeSpec.originatingElements.stream() 52 .map(element -> XConverters.toXProcessing(element, env)) 53 .collect(toImmutableList()); 54 55 StringBuilder sb = new StringBuilder(""); 56 javaFile.writeTo(sb); 57 String fileContent = formatInterfaces(sb.toString(), CLASS_PATERN); 58 59 try (OutputStream outputStream = env.getFiler() 60 .writeSource( 61 javaFile.packageName, 62 javaFile.typeSpec.name, 63 "java", 64 originatingElements, 65 Mode.Isolating); 66 Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) { 67 writer.write(fileContent); 68 } 69 } 70 formatInterfaces(String content, Pattern pattern)71 private static String formatInterfaces(String content, Pattern pattern) { 72 Matcher matcher = pattern.matcher(content); 73 StringBuffer sb = new StringBuffer(content.length()); 74 while (matcher.find()) { 75 MatchResult result = matcher.toMatchResult(); 76 String spaces = result.group(1); 77 String prefix = result.group(2); 78 String interfaces = result.group(3); 79 String formattedInterfaces = formatInterfaces(spaces, interfaces); 80 matcher.appendReplacement( 81 sb, Matcher.quoteReplacement(spaces + prefix + formattedInterfaces)); 82 } 83 matcher.appendTail(sb); 84 return sb.toString(); 85 } 86 formatInterfaces(String prefixSpaces, String interfaces)87 private static String formatInterfaces(String prefixSpaces, String interfaces) { 88 StringBuilder sb = new StringBuilder(interfaces); 89 String newLine = String.format("\n%s ", prefixSpaces); 90 91 // Add a line break after each interface so that there's only 1 interface per line. 92 int i = 0; 93 int bracketCount = 0; 94 while (i >= 0 && i < sb.length()) { 95 char c = sb.charAt(i++); 96 if (c == '<') { 97 bracketCount++; 98 } else if (c == '>') { 99 bracketCount--; 100 } else if (c == ',' && bracketCount == 0) { 101 sb.insert(i++, newLine); 102 } 103 } 104 return sb.toString(); 105 } 106 RootFileFormatter()107 private RootFileFormatter() {} 108 } 109