com.google.errorprone.RefactoringCollection.java Source code

Java tutorial

Introduction

Here is the source code for com.google.errorprone.RefactoringCollection.java

Source

/*
 * Copyright 2016 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.errorprone;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.auto.value.AutoValue;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.errorprone.apply.DescriptionBasedDiff;
import com.google.errorprone.apply.DiffApplier;
import com.google.errorprone.apply.FileDestination;
import com.google.errorprone.apply.FileSource;
import com.google.errorprone.apply.FsFileDestination;
import com.google.errorprone.apply.FsFileSource;
import com.google.errorprone.apply.PatchFileDestination;
import com.google.errorprone.matchers.Description;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.util.Log;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;

/** A container of fixes that have been collected during a single compilation phase. */
class RefactoringCollection implements DescriptionListener.Factory {
    private final Multimap<URI, DelegatingDescriptionListener> foundSources = HashMultimap.create();
    private final AtomicBoolean foundMatches = new AtomicBoolean(false);
    private final Path rootPath;
    private final FileDestination fileDestination;
    private final Callable<RefactoringResult> postProcess;
    private final DescriptionListener.Factory descriptionsFactory;

    @AutoValue
    abstract static class RefactoringResult {
        abstract String message();

        abstract RefactoringResultType type();

        private static RefactoringResult create(String message, RefactoringResultType type) {
            return new AutoValue_RefactoringCollection_RefactoringResult(message, type);
        }
    }

    enum RefactoringResultType {
        NO_CHANGES, CHANGED,
    }

    static RefactoringCollection refactorInPlace() {
        Path rootPath = buildRootPath();
        return new RefactoringCollection(rootPath, new FsFileDestination(rootPath),
                () -> RefactoringResult
                        .create("Refactoring changes were successfully applied, please check the refactored code "
                                + "and recompile.", RefactoringResultType.CHANGED));
    }

    static RefactoringCollection refactorToPatchFile(String baseDirectory) {
        Path rootPath = buildRootPath();
        Path baseDir = rootPath.resolve(baseDirectory);
        Path patchFilePath = baseDir.resolve("error-prone.patch");

        PatchFileDestination patchFileDestination = new PatchFileDestination(baseDir, rootPath);
        Callable<RefactoringResult> postProcess = () -> {
            try {
                writePatchFile(patchFileDestination, patchFilePath);
                return RefactoringResult.create(
                        "Changes were written to " + patchFilePath
                                + ". Please inspect the file and apply with: patch -p0 -u -i error-prone.patch",
                        RefactoringResultType.CHANGED);
            } catch (IOException e) {
                throw new RuntimeException("Failed to emit patch file!", e);
            }
        };
        return new RefactoringCollection(rootPath, patchFileDestination, postProcess);
    }

    private RefactoringCollection(Path rootPath, FileDestination fileDestination,
            Callable<RefactoringResult> postProcess) {
        this.rootPath = rootPath;
        this.fileDestination = fileDestination;
        this.postProcess = postProcess;
        this.descriptionsFactory = JavacErrorDescriptionListener.providerForRefactoring();
    }

    private static Path buildRootPath() {
        Path root = Iterables.getFirst(FileSystems.getDefault().getRootDirectories(), null);
        if (root == null) {
            throw new RuntimeException("Can't find a root filesystem!");
        }
        return root;
    }

    @Override
    public DescriptionListener getDescriptionListener(Log log, JCCompilationUnit compilation) {
        URI sourceFile = compilation.getSourceFile().toUri();

        DelegatingDescriptionListener delegate = new DelegatingDescriptionListener(
                descriptionsFactory.getDescriptionListener(log, compilation),
                DescriptionBasedDiff.createIgnoringOverlaps(compilation));
        foundSources.put(sourceFile, delegate);
        return delegate;
    }

    RefactoringResult applyChanges() throws Exception {
        if (!foundMatches.get()) {
            return RefactoringResult.create("", RefactoringResultType.NO_CHANGES);
        }

        doApplyProcess(fileDestination, new FsFileSource(rootPath));
        return postProcess.call();
    }

    private static void writePatchFile(PatchFileDestination fileDestination, Path patchFilePatch)
            throws IOException {
        String patchFile = fileDestination.patchFile();
        if (!patchFile.isEmpty()) {
            Files.write(patchFilePatch, patchFile.getBytes(UTF_8));
        }
    }

    private void doApplyProcess(FileDestination fileDestination, FileSource fileSource) {
        DiffApplier diffApplier = new DiffApplier(4, fileSource, fileDestination);
        diffApplier.startAsync().awaitRunning();

        List<Future<?>> futures = new ArrayList<>();
        for (DelegatingDescriptionListener listener : foundSources.values()) {
            futures.add(diffApplier.put(listener.base));
        }
        diffApplier.stopAsync().awaitTerminated();

        try {
            for (Future<?> future : futures) {
                future.get();
            }
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    private final class DelegatingDescriptionListener implements DescriptionListener {
        final DescriptionBasedDiff base;
        final DescriptionListener listener;

        DelegatingDescriptionListener(DescriptionListener listener, DescriptionBasedDiff base) {
            this.listener = listener;
            this.base = base;
        }

        @Override
        public void onDescribed(Description description) {
            foundMatches.set(true);
            listener.onDescribed(description);
            base.onDescribed(description);
        }
    }
}