/** * Copyright (C) 2013-2014 Olaf Lessenich * Copyright (C) 2014-2015 University of Passau, Germany * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * Contributors: * Olaf Lessenich <lessenic@fim.uni-passau.de> * Georg Seibt <seibt@fim.uni-passau.de> */ package de.fosd.jdime.strategy; import java.io.FileWriter; import java.io.IOException; import java.security.Permission; import java.util.logging.Level; import java.util.logging.Logger; import de.fosd.jdime.artifact.ast.ASTNodeArtifact; import de.fosd.jdime.artifact.file.FileArtifact; import de.fosd.jdime.config.merge.MergeContext; import de.fosd.jdime.config.merge.MergeScenario; import de.fosd.jdime.operations.MergeOperation; import de.fosd.jdime.stats.MergeScenarioStatistics; import de.fosd.jdime.stats.Statistics; import de.fosd.jdime.stats.StatisticsInterface; import de.fosd.jdime.stats.parser.ParseResult; import static de.fosd.jdime.strdump.DumpMode.GRAPHVIZ_TREE; import static de.fosd.jdime.strdump.DumpMode.PLAINTEXT_TREE; /** * Performs a structured merge on <code>FileArtifacts</code>. * * @author Olaf Lessenich */ public class StructuredStrategy extends MergeStrategy<FileArtifact> { private static final Logger LOG = Logger.getLogger(StructuredStrategy.class.getCanonicalName()); private SecurityManager systemSecurityManager = System.getSecurityManager(); private SecurityManager noExitManager = new SecurityManager() { @Override public void checkPermission(Permission perm) { // allow anything. } @Override public void checkPermission(Permission perm, Object context) { // allow anything. } @Override public void checkExit(int status) { super.checkExit(status); throw new SecurityException("Captured attempt to exit JVM."); } }; /** * The source <code>FileArtifacts</code> are extracted from the * <code>MergeOperation</code>, parsed by the <code>JastAddJ</code> parser * into abstract syntax trees, and on the fly encapsulated into * <code>ASTNodeArtifacts</code>. * <p> * A new <code>MergeOperation</code>, encapsulating * <code>ASTNodeArtifacts</code> as source and target nodes, is created and applied. * * TODO: more high-level documentation. * * @param operation the <code>MergeOperation</code> to perform * @param context the <code>MergeContext</code> */ @Override public void merge(MergeOperation<FileArtifact> operation, MergeContext context) { /** * The method creates ASTNodeArtifacts from the input files. An ASTNodeStrategy is then applied. * The result is pretty printed and possibly written to the output file. */ MergeScenario<FileArtifact> triple = operation.getMergeScenario(); FileArtifact leftFile = triple.getLeft(); FileArtifact rightFile = triple.getRight(); FileArtifact baseFile = triple.getBase(); FileArtifact target = null; String lPath = leftFile.getPath(); String bPath = baseFile.getPath(); String rPath = rightFile.getPath(); if (!context.isDiffOnly() && operation.getTarget() != null) { target = operation.getTarget(); if (target.exists() && !target.isEmpty()) { throw new AssertionError(String.format("Would be overwritten: %s", target)); } } context.resetStreams(); System.setSecurityManager(noExitManager); LOG.fine(() -> String.format("Merging:%nLeft: %s%nBase: %s%nRight: %s", lPath, bPath, rPath)); try { long startTime = System.currentTimeMillis(); ASTNodeArtifact left = new ASTNodeArtifact(leftFile); ASTNodeArtifact base = new ASTNodeArtifact(baseFile); ASTNodeArtifact right = new ASTNodeArtifact(rightFile); ASTNodeArtifact targetNode = ASTNodeArtifact.createProgram(left); String lCond = left.getRevision().getName(); String rCond = right.getRevision().getName(); MergeScenario<ASTNodeArtifact> nodeTriple = new MergeScenario<>(triple.getMergeType(), left, base, right); MergeOperation<ASTNodeArtifact> astMergeOp = new MergeOperation<>(nodeTriple, targetNode, lCond, rCond); LOG.finest(() -> String.format("Tree dump of target node:%n%s", targetNode.dump(PLAINTEXT_TREE))); LOG.finest(() -> String.format("MergeScenario:%n%s", nodeTriple.toString())); LOG.finest("Applying an ASTNodeArtifact MergeOperation."); astMergeOp.apply(context); targetNode.setRevision(MergeScenario.TARGET, true); // TODO do this somewhere else? long runtime = System.currentTimeMillis() - startTime; LOG.fine("Structured merge finished."); if (!context.isDiffOnly()) { LOG.finest(() -> String.format("Tree dump of target node:%n%s", targetNode.dump(PLAINTEXT_TREE))); } LOG.finest(() -> String.format("Pretty-printing left:%n%s", left.prettyPrint())); LOG.finest(() -> String.format("Pretty-printing right:%n%s", right.prettyPrint())); if (!context.isDiffOnly()) { context.appendLine(targetNode.prettyPrint()); LOG.finest(() -> String.format("Pretty-printing merge result:%n%s", context.getStdIn())); } LOG.fine(() -> String.format("%s merge time was %d ms.", getClass().getSimpleName(), runtime)); if (context.hasErrors()) { LOG.severe(() -> String.format("Errors occurred while merging structurally.%n%s", context.getStdErr())); } if (!context.isPretend() && target != null) { LOG.fine("Writing output to: " + target.getFullPath()); target.write(context.getStdIn()); } if (context.hasStatistics()) { if (LOG.isLoggable(Level.FINE)) { String fileName = leftFile + ".dot"; LOG.fine("Dumping the target node tree to " + fileName); try (FileWriter fw = new FileWriter(fileName)) { fw.write(targetNode.dump(GRAPHVIZ_TREE)); } catch (IOException e) { LOG.log(Level.WARNING, e, () -> "Can not write the graphviz representation of " + leftFile); } } Statistics statistics = context.getStatistics(); MergeScenarioStatistics scenarioStatistics = new MergeScenarioStatistics(triple); if (!context.isDiffOnly()) { ParseResult parseResult = scenarioStatistics.setLineStatistics(context.getStdIn()); if (parseResult.getConflicts() > 0) { scenarioStatistics.getFileStatistics().incrementNumOccurInConflic(); } } scenarioStatistics.add(StatisticsInterface.getASTStatistics(left, right.getRevision())); scenarioStatistics.add(StatisticsInterface.getASTStatistics(right, left.getRevision())); scenarioStatistics.add(StatisticsInterface.getASTStatistics(targetNode, null)); scenarioStatistics.setRuntime(runtime); statistics.addScenarioStatistics(scenarioStatistics); } } finally { System.setSecurityManager(systemSecurityManager); } } }