com.google.devtools.kythe.analyzers.jvm.ClassFileIndexer.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.kythe.analyzers.jvm.ClassFileIndexer.java

Source

/*
 * Copyright 2018 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.devtools.kythe.analyzers.jvm;

import static com.google.devtools.kythe.extractors.jvm.JvmExtractor.CLASS_FILE_EXT;
import static com.google.devtools.kythe.extractors.jvm.JvmExtractor.JAR_DETAILS_URL;
import static com.google.devtools.kythe.extractors.jvm.JvmExtractor.JAR_ENTRY_DETAILS_URL;
import static com.google.devtools.kythe.extractors.jvm.JvmExtractor.JAR_FILE_EXT;

import com.beust.jcommander.Parameter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.kythe.analyzers.base.FactEmitter;
import com.google.devtools.kythe.analyzers.base.IndexerConfig;
import com.google.devtools.kythe.analyzers.base.StreamFactEmitter;
import com.google.devtools.kythe.extractors.shared.CompilationDescription;
import com.google.devtools.kythe.extractors.shared.IndexInfoUtils;
import com.google.devtools.kythe.platform.shared.AnalysisException;
import com.google.devtools.kythe.platform.shared.FileDataCache;
import com.google.devtools.kythe.platform.shared.FileDataProvider;
import com.google.devtools.kythe.platform.shared.MemoryStatisticsCollector;
import com.google.devtools.kythe.platform.shared.NullStatisticsCollector;
import com.google.devtools.kythe.platform.shared.StatisticsCollector;
import com.google.devtools.kythe.proto.Analysis.CompilationUnit;
import com.google.devtools.kythe.proto.Analysis.FileInfo;
import com.google.devtools.kythe.proto.Java.JarDetails;
import com.google.devtools.kythe.proto.Java.JarEntryDetails;
import com.google.devtools.kythe.proto.Storage.VName;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Kythe analyzer for JVM class files (possibly within a jar or kindex file).
 *
 * <p>Usage: class_file_indexer <class_file | jar_file | kindex_file> ...
 */
public class ClassFileIndexer {
    private static final FluentLogger logger = FluentLogger.forEnclosingClass();

    public static void main(String[] args) throws AnalysisException {
        StandaloneConfig config = new StandaloneConfig();
        config.parseCommandLine(args);

        try (OutputStream stream = Strings.isNullOrEmpty(config.getOutputPath()) ? System.out
                : new BufferedOutputStream(new FileOutputStream(config.getOutputPath()))) {
            FactEmitter emitter = new StreamFactEmitter(stream);
            MemoryStatisticsCollector statistics = config.getPrintStatistics() ? new MemoryStatisticsCollector()
                    : null;
            KytheClassVisitor classVisitor = new KytheClassVisitor(
                    statistics == null ? NullStatisticsCollector.getInstance() : statistics, emitter);
            for (String fileName : config.getFilesToIndex()) {
                File file = new File(fileName);
                if (fileName.endsWith(JAR_FILE_EXT)) {
                    visitJarClassFiles(file, classVisitor);
                } else if (fileName.endsWith(CLASS_FILE_EXT)) {
                    visitClassFile(file, classVisitor);
                } else if (fileName.endsWith(IndexInfoUtils.INDEX_FILE_EXT)) {
                    CompilationDescription desc = IndexInfoUtils.readIndexInfoFromFile(fileName);
                    analyzeCompilation(desc.getCompilationUnit(), new FileDataCache(desc.getFileContents()),
                            classVisitor);
                } else {
                    throw new IllegalArgumentException("unknown file path extension: " + fileName);
                }
            }
            if (statistics != null) {
                statistics.printStatistics(System.err);
            }
        } catch (IOException ioe) {
            throw new AnalysisException("error writing output", ioe);
        }
    }

    /** Analyze each class file contained within the {@link CompilationUnit}. */
    public static void analyzeCompilation(StatisticsCollector statistics, CompilationUnit compilationUnit,
            FileDataProvider fileDataProvider, FactEmitter emitter) throws AnalysisException {
        KytheClassVisitor classVisitor = new KytheClassVisitor(statistics, emitter);
        analyzeCompilation(compilationUnit, fileDataProvider, classVisitor);
    }

    private static void analyzeCompilation(CompilationUnit compilationUnit, FileDataProvider fileDataProvider,
            KytheClassVisitor classVisitor) throws AnalysisException {
        ImmutableList<VName> enclosingJars = createJarIndex(compilationUnit);
        for (CompilationUnit.FileInput file : compilationUnit.getRequiredInputList()) {
            FileInfo info = file.getInfo();
            if (info.getPath().endsWith(CLASS_FILE_EXT)) {
                classVisitor = classVisitor.withEnclosingJarFile(getEnclosingJar(enclosingJars, file));
                try {
                    ListenableFuture<byte[]> contents = fileDataProvider.startLookup(info);
                    classVisitor.visitClassFile(contents.get());
                } catch (ExecutionException | InterruptedException e) {
                    throw new AnalysisException("error retrieving file contents for " + info, e);
                }
            }
        }
    }

    private static ImmutableList<VName> createJarIndex(CompilationUnit compilationUnit) {
        JarDetails jarDetails = null;
        for (Any details : compilationUnit.getDetailsList()) {
            if (details.getTypeUrl().equals(JAR_DETAILS_URL)) {
                try {
                    jarDetails = JarDetails.parseFrom(details.getValue());
                } catch (InvalidProtocolBufferException ipbe) {
                    logger.atWarning().withCause(ipbe).log("Error unpacking JarDetails");
                }
            }
        }
        return jarDetails.getJarList().stream().map(JarDetails.Jar::getVName)
                .collect(ImmutableList.toImmutableList());
    }

    private static VName getEnclosingJar(ImmutableList<VName> enclosingJars, CompilationUnit.FileInput file) {
        JarEntryDetails jarEntryDetails = null;
        for (Any details : file.getDetailsList()) {
            if (details.getTypeUrl().equals(JAR_ENTRY_DETAILS_URL)) {
                try {
                    jarEntryDetails = JarEntryDetails.parseFrom(details.getValue());
                } catch (InvalidProtocolBufferException ipbe) {
                    logger.atWarning().withCause(ipbe).log("Error unpacking JarEntryDetails");
                }
            }
        }
        if (jarEntryDetails == null) {
            return null;
        }
        int idx = jarEntryDetails.getJarContainer();
        if (idx < 0 || idx >= enclosingJars.size()) {
            logger.atWarning().log("JarEntryDetails index out of range: %s (jars: %s)", jarEntryDetails,
                    enclosingJars);
            return null;
        }
        return enclosingJars.get(idx);
    }

    private static void visitJarClassFiles(File jarFile, KytheClassVisitor visitor) throws AnalysisException {
        try (JarFile jar = new JarFile(jarFile)) {
            for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();) {
                JarEntry entry = entries.nextElement();
                if (!entry.getName().endsWith(CLASS_FILE_EXT)) {
                    continue;
                }
                try (InputStream input = jar.getInputStream(entry)) {
                    visitor.visitClassFile(input);
                } catch (IOException ioe) {
                    throw new AnalysisException("error reading class file: " + entry.getName(), ioe);
                }
            }
        } catch (IOException ioe) {
            throw new AnalysisException("error reading jar file: " + jarFile, ioe);
        }
    }

    private static void visitClassFile(File classFile, KytheClassVisitor visitor) throws AnalysisException {
        try (InputStream input = new FileInputStream(classFile)) {
            visitor.visitClassFile(input);
        } catch (IOException ioe) {
            throw new AnalysisException("error reading class file: " + classFile, ioe);
        }
    }

    private static class StandaloneConfig extends IndexerConfig {
        @Parameter(description = "<jar|class|kindex files to analyze>", required = true)
        private List<String> filesToIndex = new ArrayList<>();

        @Parameter(names = "--print_statistics", description = "Print final analyzer statistics to stderr")
        private boolean printStatistics;

        @Parameter(names = { "--out",
                "-out" }, description = "Write the entries to this file (or stdout if unspecified)")
        private String outputPath;

        public StandaloneConfig() {
            super("classfile-indexer");
        }

        public final boolean getPrintStatistics() {
            return printStatistics;
        }

        public final String getOutputPath() {
            return outputPath;
        }

        public final List<String> getFilesToIndex() {
            return filesToIndex;
        }
    }
}