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.KtApiKt;
025import com.microsoft.thrifty.protocol.Protocol;
026import com.microsoft.thrifty.transport.Transport;
027import okio.BufferedSource;
028import okio.GzipSource;
029import okio.Okio;
030import okio.Source;
031import org.gradle.api.logging.LogLevel;
032import org.gradle.workers.WorkAction;
033import org.jetbrains.annotations.NotNull;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import java.io.File;
038import java.io.IOException;
039import java.io.PrintWriter;
040import java.io.Writer;
041
042public abstract class ReportOutputWorker implements WorkAction<ReportOutputWorkerParams> {
043    private static final Logger LOGGER = LoggerFactory.getLogger(ReportOutputWorker.class);
044
045    @Override
046    public void execute() {
047        try {
048            actuallyExecute();
049        } catch (IOException e) {
050            LOGGER.error("Error reporting dexcount output; please clean and rebuild.", e);
051        }
052    }
053
054    private void actuallyExecute() throws IOException {
055        TreeGenOutput treeGen = readTreeGenFile();
056        if (treeGen.tree == null) {
057            LOGGER.error("Corrupted dexcount data; please clean and rebuild.");
058            return;
059        }
060
061        String inputRepresentation = treeGen.inputRepresentation;
062        if (inputRepresentation == null) {
063            LOGGER.error("Corrupted dexcount data; please clean and rebuild.");
064            return;
065        }
066
067        PackageTree tree = PackageTree.fromThrift(treeGen.tree);
068        CountReporter reporter = new CountReporter(
069            tree,
070            getParameters().getVariantName().get(),
071            new Slf4jStyleable(LOGGER),
072            getParameters().getPrintOptions().get(),
073            inputRepresentation,
074            false);
075
076        reporter.report();
077    }
078
079    private TreeGenOutput readTreeGenFile() throws IOException {
080        File file = getParameters().getPackageTreeFile().getAsFile().get();
081
082        try (
083            Source source = Okio.source(file);
084            GzipSource gzip = new GzipSource(source);
085            BufferedSource bufferedSource = Okio.buffer(gzip);
086            Transport transport = KtApiKt.transport(bufferedSource);
087            Protocol protocol = KtApiKt.compactProtocol(transport)
088        ) {
089            return TreeGenOutput.ADAPTER.read(protocol);
090        }
091    }
092
093    private static class Slf4jStyleable implements Styleable {
094        private final Logger logger;
095
096        Slf4jStyleable(Logger logger) {
097            this.logger = logger;
098        }
099
100        @Override
101        public void withStyledOutput(Color color, LogLevel level, IOConsumer<PrintWriter> fn) throws IOException {
102            LoggerWriterAdapter adapter = new LoggerWriterAdapter(logger, color, level);
103            try (PrintWriter pw = new PrintWriter(adapter)) {
104                fn.accept(pw);
105                pw.flush();
106            }
107        }
108    }
109
110    private static class LoggerWriterAdapter extends Writer {
111        private final Logger logger;
112        private final Color color;
113        private final LogLevel logLevel;
114
115        LoggerWriterAdapter(Logger logger, Color color, LogLevel logLevel) {
116            this.logger = logger;
117            this.color = color;
118            this.logLevel = logLevel != null ? logLevel : LogLevel.WARN;
119        }
120
121        @Override
122        public void write(@NotNull char[] chars, int length, int offset) throws IOException {
123            String message = new String(chars, length, offset);
124            if (System.lineSeparator().equals(message)) {
125                // Dumb hack to work around PrintWriter.println breaking Gradle's stdout
126                // as mediated through SLF4J.
127                //
128                // Basically, this completely eliminates the newline from println calls
129                // and relies on SLF4J's (Gradle's?) behavior of appending the newline.
130                //
131                // Without this hack, one println results in three or four newlines.
132                return;
133            }
134
135            switch (logLevel) {
136                case DEBUG:
137                    logger.debug(message);
138                    break;
139
140                case INFO:
141                case LIFECYCLE:
142                    logger.info(message);
143                    break;
144
145                case WARN:
146                case QUIET:
147                    logger.warn(message);
148                    break;
149
150                case ERROR:
151                    logger.error(message);
152                    break;
153            }
154        }
155
156        @Override
157        public void flush() {
158
159        }
160
161        @Override
162        public void close() {
163
164        }
165    }
166}