All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sorald.Repair Maven / Gradle / Ivy

package sorald;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import sorald.event.EventHelper;
import sorald.event.EventType;
import sorald.event.SoraldEventHandler;
import sorald.event.collectors.CompilationUnitCollector;
import sorald.event.models.CrashEvent;
import sorald.processor.SoraldAbstractProcessor;
import sorald.segment.FirstFitSegmentationAlgorithm;
import sorald.segment.Node;
import sorald.segment.SoraldTreeBuilderAlgorithm;
import sorald.sonar.BestFitScanner;
import sorald.sonar.RuleViolation;
import spoon.Launcher;
import spoon.MavenLauncher;
import spoon.compiler.Environment;
import spoon.processing.ProcessingManager;
import spoon.processing.Processor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.DefaultImportComparator;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.ImportCleaner;
import spoon.reflect.visitor.ImportConflictDetector;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.support.QueueProcessingManager;
import spoon.support.sniper.SniperJavaPrettyPrinter;

/** Class for repairing projects. */
public class Repair {
    private final SoraldConfig config;

    final List eventHandlers;
    private final CompilationUnitCollector cuCollector;

    public Repair(SoraldConfig config, List eventHandlers) {
        this.config = config;
        cuCollector = new CompilationUnitCollector();
        List eventHandlersCopy = new ArrayList<>(eventHandlers);
        eventHandlersCopy.add(cuCollector);
        this.eventHandlers = Collections.unmodifiableList(eventHandlersCopy);
    }

    /**
     * Execute a repair according to the config.
     *
     * @param ruleViolations Rule violations to repair. May not be empty, and must relate to a
     *     single rule.
     * @return The processor used in the repairs.
     * @throws IllegalArgumentException if the supplied rule violations are empty, or relate to
     *     multiple rules.
     */
    public SoraldAbstractProcessor repair(Set ruleViolations) {
        List distinctRuleKeys =
                ruleViolations.stream()
                        .map(RuleViolation::getRuleKey)
                        .distinct()
                        .collect(Collectors.toList());
        if (distinctRuleKeys.size() != 1) {
            throw new IllegalArgumentException(
                    "expected rule violations for precisely 1 rule key, got: " + distinctRuleKeys);
        }

        String ruleKey = distinctRuleKeys.get(0);
        Path inputDir = Path.of(config.getSource());

        SoraldAbstractProcessor processor = createProcessor(Integer.parseInt(ruleKey));
        Stream models = repair(inputDir, processor, ruleViolations);

        models.forEach(
                model -> {
                    cuCollector
                            .getCollectedCompilationUnits()
                            .forEach(this::overwriteCompilationUnit);
                    cuCollector.clear();
                });

        return processor;
    }

    Stream repair(
            Path inputDir, SoraldAbstractProcessor processor, Set violations) {
        switch (config.getRepairStrategy()) {
            case DEFAULT:
                return Stream.of(defaultRepair(inputDir, processor, violations));
            case MAVEN:
                return Stream.of(mavenRepair(inputDir, processor, violations));
            case SEGMENT:
                return segmentRepair(
                        inputDir,
                        processor,
                        violations,
                        segment -> createSegmentLauncher(segment).getModel());
            default:
                throw new IllegalStateException(
                        "unknown repair strategy: " + config.getRepairStrategy());
        }
    }

    CtModel defaultRepair(
            Path inputDir, SoraldAbstractProcessor processor, Set violations) {
        EventHelper.fireEvent(EventType.PARSE_START, eventHandlers);
        Launcher launcher = new Launcher();
        launcher.addInputResource(inputDir.toString());
        CtModel model = initLauncher(launcher).getModel();
        EventHelper.fireEvent(EventType.PARSE_END, eventHandlers);

        repairModelWithInitializedProcessor(model, processor, violations);
        return model;
    }

    CtModel mavenRepair(
            Path inputDir, SoraldAbstractProcessor processor, Set violations) {
        EventHelper.fireEvent(EventType.PARSE_START, eventHandlers);
        MavenLauncher launcher =
                new MavenLauncher(inputDir.toString(), MavenLauncher.SOURCE_TYPE.ALL_SOURCE);
        CtModel model = initLauncher(launcher).getModel();
        EventHelper.fireEvent(EventType.PARSE_END, eventHandlers);

        repairModelWithInitializedProcessor(model, processor, violations);
        return model;
    }

    Stream segmentRepair(
            Path inputDir,
            SoraldAbstractProcessor processor,
            Set violations,
            Function, CtModel> parseSegment) {
        Node rootNode = SoraldTreeBuilderAlgorithm.buildTree(inputDir.toString());
        LinkedList> segments =
                FirstFitSegmentationAlgorithm.segment(rootNode, config.getMaxFilesPerSegment());

        return segments.stream()
                .map(
                        segment -> {
                            try {
                                EventHelper.fireEvent(EventType.PARSE_START, eventHandlers);
                                CtModel model = parseSegment.apply(segment);
                                EventHelper.fireEvent(EventType.PARSE_END, eventHandlers);

                                repairModelWithInitializedProcessor(model, processor, violations);
                                return model;
                            } catch (Exception e) {
                                reportSegmentCrash(segment, e);
                                e.printStackTrace();
                                return null;
                            }
                        })
                .filter(Objects::nonNull)
                .takeWhile(model -> processor.getNbFixes() < config.getMaxFixesPerRule());
    }

    private void reportSegmentCrash(LinkedList segment, Exception e) {
        List paths =
                segment.stream()
                        .map(
                                node ->
                                        node.isDirNode()
                                                ? node.getRootPath()
                                                : node.getJavaFiles().toString())
                        .collect(Collectors.toList());
        EventHelper.fireEvent(new CrashEvent("Crash in segment: " + paths, e), eventHandlers);
    }

    private void repairModelWithInitializedProcessor(
            CtModel model, SoraldAbstractProcessor processor, Set violations) {
        EventHelper.fireEvent(EventType.REPAIR_START, eventHandlers);
        var bestFits = new IdentityHashMap();
        model.getAllModules().stream()
                .map(module -> BestFitScanner.calculateBestFits(module, violations, processor))
                .flatMap(m -> m.entrySet().stream())
                .forEach(entry -> bestFits.put(entry.getKey(), entry.getValue()));
        processor.setBestFits(bestFits);

        Factory factory = model.getUnnamedModule().getFactory();
        ProcessingManager processingManager = new QueueProcessingManager(factory);
        processingManager.addProcessor(processor);
        processingManager.process(factory.Class().getAll());
        EventHelper.fireEvent(EventType.REPAIR_END, eventHandlers);
    }

    Launcher createSegmentLauncher(List segment) {
        Launcher launcher = new Launcher();

        for (Node node : segment) {
            if (node.isDirNode()) {
                launcher.addInputResource(node.getRootPath());
            } else {
                for (String file : node.getJavaFiles()) {
                    launcher.addInputResource(file);
                }
            }
        }
        return initLauncher(launcher);
    }

    private void overwriteCompilationUnit(CtCompilationUnit cu) {
        List> typesToPrint =
                cu.getDeclaredTypes().stream()
                        .filter(CtType::isTopLevel)
                        .collect(Collectors.toList());
        Path sourcePath = cu.getPosition().getFile().toPath();

        String output =
                cu.getFactory()
                        .getEnvironment()
                        .createPrettyPrinter()
                        .printTypes(typesToPrint.toArray(CtType[]::new));

        // we overwrite the source
        writeToFile(sourcePath, output);
    }

    private static void writeToFile(Path filepath, String output) {
        File dir = filepath.getParent().toFile();
        if (!(dir.isDirectory() || dir.mkdirs())) {
            throw new IllegalStateException("could not create directory '" + dir + "'");
        }

        try {
            Files.writeString(filepath, output);
        } catch (IOException e) {
            // must convert to a runtime exception as this is used in writeModel, which in turn
            // is used in a stream (that can't have checked exceptions)
            throw new RuntimeException(e);
        }
    }

    private Launcher initLauncher(Launcher launcher) {
        Environment env = launcher.getEnvironment();
        env.setIgnoreDuplicateDeclarations(true);
        env.setComplianceLevel(Constants.DEFAULT_COMPLIANCE_LEVEL);

        // this is a workaround for https://github.com/INRIA/spoon/issues/3693
        if (config.getPrettyPrintingStrategy() == PrettyPrintingStrategy.SNIPER) {
            env.setPrettyPrinterCreator(() -> new SniperJavaPrettyPrinter(env));
        }

        // need to build the model before setting the pretty-printer as the preprocessors need
        // data from the model
        CtModel model = launcher.buildModel();

        setPrettyPrinter(env, model);
        return launcher;
    }

    private void setPrettyPrinter(Environment env, CtModel model) {
        Supplier basePrinterCreator =
                config.getPrettyPrintingStrategy() == PrettyPrintingStrategy.SNIPER
                        ? createSniperPrinter(env)
                        : createDefaultPrinter(env);
        Supplier configuredPrinterCreator =
                applyCommonPrinterOptions(basePrinterCreator, model);
        env.setPrettyPrinterCreator(configuredPrinterCreator);
    }

    private static Supplier applyCommonPrinterOptions(
            Supplier prettyPrinterCreator, CtModel model) {
        Collection> existingReferences = model.getElements(e -> true);
        List> preprocessors =
                List.of(
                        new SelectiveForceImport(existingReferences),
                        new ImportConflictDetector(),
                        new ImportCleaner().setImportComparator(new DefaultImportComparator()));
        return () -> {
            DefaultJavaPrettyPrinter printer = prettyPrinterCreator.get();
            printer.setIgnoreImplicit(false);
            printer.setPreprocessors(preprocessors);
            return printer;
        };
    }

    private static Supplier createSniperPrinter(
            Environment env) {
        env.setCommentEnabled(true);
        env.useTabulations(true);
        env.setTabulationSize(4);
        return () -> new SniperJavaPrettyPrinter(env);
    }

    private static Supplier createDefaultPrinter(
            Environment env) {
        return () -> new DefaultJavaPrettyPrinter(env);
    }

    private SoraldAbstractProcessor createBaseProcessor(Integer ruleKey) {
        try {
            Class processor = Processors.getProcessor(ruleKey);
            if (processor != null) {
                Constructor cons = processor.getConstructor();
                return (SoraldAbstractProcessor) cons.newInstance();
            }
        } catch (InstantiationException
                | IllegalAccessException
                | InvocationTargetException
                | NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private SoraldAbstractProcessor createProcessor(Integer ruleKey) {
        SoraldAbstractProcessor processor = createBaseProcessor(ruleKey);
        if (processor != null) {
            return processor
                    .setMaxFixes(config.getMaxFixesPerRule())
                    .setEventHandlers(eventHandlers);
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy