001/* 002 * Copyright (C) 2015-2021 KeepSafe Software 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.getkeepsafe.dexcount.report; 017 018import com.getkeepsafe.dexcount.CountReporter; 019import com.getkeepsafe.dexcount.PackageTree; 020import com.getkeepsafe.dexcount.colors.Color; 021import com.getkeepsafe.dexcount.colors.IOConsumer; 022import com.getkeepsafe.dexcount.colors.Styleable; 023import com.getkeepsafe.dexcount.thrift.TreeGenOutput; 024import com.microsoft.thrifty.protocol.CompactProtocol; 025import com.microsoft.thrifty.protocol.Protocol; 026import com.microsoft.thrifty.transport.Transport; 027import okio.BufferedSource; 028import okio.GzipSource; 029import okio.Okio; 030import org.gradle.api.logging.LogLevel; 031import org.gradle.workers.WorkAction; 032import org.jetbrains.annotations.NotNull; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import java.io.File; 037import java.io.IOException; 038import java.io.PrintWriter; 039import java.io.Writer; 040 041public abstract class ReportOutputWorker implements WorkAction<ReportOutputWorkerParams> { 042 private static final Logger LOGGER = LoggerFactory.getLogger(ReportOutputWorker.class); 043 044 @Override 045 public void execute() { 046 try { 047 actuallyExecute(); 048 } catch (IOException e) { 049 LOGGER.error("Error reporting dexcount output; please clean and rebuild.", e); 050 } 051 } 052 053 private void actuallyExecute() throws IOException { 054 TreeGenOutput treeGen = readTreeGenFile(); 055 if (treeGen.tree == null) { 056 LOGGER.error("Corrupted dexcount data; please clean and rebuild."); 057 return; 058 } 059 060 String inputRepresentation = treeGen.inputRepresentation; 061 if (inputRepresentation == null) { 062 LOGGER.error("Corrupted dexcount data; please clean and rebuild."); 063 return; 064 } 065 066 PackageTree tree = PackageTree.fromThrift(treeGen.tree); 067 CountReporter reporter = new CountReporter( 068 tree, 069 getParameters().getVariantName().get(), 070 new Slf4jStyleable(LOGGER), 071 getParameters().getPrintOptions().get(), 072 inputRepresentation, 073 false); 074 075 reporter.report(); 076 } 077 078 private TreeGenOutput readTreeGenFile() throws IOException { 079 File file = getParameters().getPackageTreeFile().getAsFile().get(); 080 BufferedSource source = Okio.buffer(new GzipSource(Okio.source(file))); 081 Transport transport = new Transport() { 082 @Override 083 public int read(byte[] buffer, int offset, int count) throws IOException { 084 return source.read(buffer, offset, count); 085 } 086 087 @Override 088 public void write(byte[] buffer, int offset, int count) { 089 throw new UnsupportedOperationException(); 090 } 091 092 @Override 093 public void flush() { 094 throw new UnsupportedOperationException(); 095 } 096 097 @Override 098 public void close() throws IOException { 099 source.close(); 100 } 101 }; 102 Protocol protocol = new CompactProtocol(transport); 103 return TreeGenOutput.ADAPTER.read(protocol); 104 } 105 106 private static class Slf4jStyleable implements Styleable { 107 private final Logger logger; 108 109 Slf4jStyleable(Logger logger) { 110 this.logger = logger; 111 } 112 113 @Override 114 public void withStyledOutput(Color color, LogLevel level, IOConsumer<PrintWriter> fn) throws IOException { 115 LoggerWriterAdapter adapter = new LoggerWriterAdapter(logger, color, level); 116 try (PrintWriter pw = new PrintWriter(adapter)) { 117 fn.accept(pw); 118 pw.flush(); 119 } 120 } 121 } 122 123 private static class LoggerWriterAdapter extends Writer { 124 private final Logger logger; 125 private final Color color; 126 private final LogLevel logLevel; 127 128 LoggerWriterAdapter(Logger logger, Color color, LogLevel logLevel) { 129 this.logger = logger; 130 this.color = color; 131 this.logLevel = logLevel != null ? logLevel : LogLevel.WARN; 132 } 133 134 @Override 135 public void write(@NotNull char[] chars, int length, int offset) throws IOException { 136 String message = new String(chars, length, offset); 137 if (System.lineSeparator().equals(message)) { 138 // Dumb hack to work around PrintWriter.println breaking Gradle's stdout 139 // as mediated through SLF4J. 140 // 141 // Basically, this completely eliminates the newline from println calls 142 // and relies on SLF4J's (Gradle's?) behavior of appending the newline. 143 // 144 // Without this hack, one println results in three or four newlines. 145 return; 146 } 147 148 switch (logLevel) { 149 case DEBUG: 150 logger.debug(message); 151 break; 152 153 case INFO: 154 case LIFECYCLE: 155 logger.info(message); 156 break; 157 158 case WARN: 159 case QUIET: 160 logger.warn(message); 161 break; 162 163 case ERROR: 164 logger.error(message); 165 break; 166 } 167 } 168 169 @Override 170 public void flush() { 171 172 } 173 174 @Override 175 public void close() { 176 177 } 178 } 179}