de.fosd.jdime.common.ASTNodeArtifact.java Source code

Java tutorial

Introduction

Here is the source code for de.fosd.jdime.common.ASTNodeArtifact.java

Source

/*
 * 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>
 *     Kathrin Hanauer <hanauer@fim.uni-passau.de>
 *     Georg Seibt <seibt@fim.uni-passau.de>
 */
package de.fosd.jdime.common;

import AST.*;
import de.fosd.jdime.common.operations.MergeOperation;
import de.fosd.jdime.matcher.Color;
import de.fosd.jdime.matcher.Matching;
import de.fosd.jdime.stats.ASTStats;
import de.fosd.jdime.stats.StatsElement;
import de.fosd.jdime.strategy.ASTNodeStrategy;
import de.fosd.jdime.strategy.MergeStrategy;
import org.apache.commons.lang3.ClassUtils;
import org.apache.log4j.Logger;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;

/**
 * @author Olaf Lessenich
 *
 */
public class ASTNodeArtifact extends Artifact<ASTNodeArtifact> {

    private static final Logger LOG = Logger.getLogger(ClassUtils.getShortClassName(ASTNodeArtifact.class));

    /**
     * Initializes parser.
     *
     * @param p
     *            program
     */
    private static void initParser(final Program p) {
        p.initJavaParser(new JavaParser() {
            @Override
            public CompilationUnit parse(final java.io.InputStream is, final String fileName)
                    throws java.io.IOException, beaver.Parser.Exception {
                return new parser.JavaParser().parse(is, fileName);
            }
        });
    }

    /**
     * Initializes a program.
     *
     * @return program
     */
    private static Program initProgram() {
        Program program = new Program();
        program.state().reset();
        program.initBytecodeReader(new BytecodeParser());
        initParser(program);
        return program;
    }

    /**
     * @param artifact
     *            artifact to create program from
     * @return ASTNodeArtifact
     */
    public static ASTNodeArtifact createProgram(final ASTNodeArtifact artifact) {
        assert (artifact.astnode != null);
        assert (artifact.astnode instanceof Program);

        Program old = (Program) artifact.astnode;
        Program program;
        try {
            program = old.clone();
        } catch (CloneNotSupportedException e) {
            program = new Program();
        }

        ASTNodeArtifact p = new ASTNodeArtifact(program);
        p.deleteChildren();

        return p;
    }

    /**
     * Encapsulated ASTNode.
     */
    private ASTNode<?> astnode = null;

    /**
     * Constructor class.
     */
    public ASTNodeArtifact() {
    }

    /**
     * @param astnode
     *            astnode
     */
    public ASTNodeArtifact(final ASTNode<?> astnode) {
        assert (astnode != null);
        this.astnode = astnode;
    }

    /**
     * Constructs an ASTNodeArtifact from a FileArtifact.
     *
     * @param artifact
     *            file artifact
     */
    public ASTNodeArtifact(final FileArtifact artifact) {
        assert (artifact != null);

        setRevision(artifact.getRevision());

        ASTNode<?> astnode;
        if (artifact.isEmptyDummy()) {
            astnode = new ASTNode<>();
            setEmptyDummy(true);
        } else {
            Program p = initProgram();
            p.addSourceFile(artifact.getPath());
            astnode = p;
        }

        this.astnode = astnode;
        renumber(1, this);
    }

    /**
     * Returns the encapsulated JastAddJ ASTNode
     *
     * @return encapsulated ASTNode object from JastAddJ
     */
    public final ASTNode<?> getASTNode() {
        return astnode;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * de.fosd.jdime.common.Artifact#addChild(de.fosd.jdime.common.Artifact)
     */
    @Override
    public final ASTNodeArtifact addChild(final ASTNodeArtifact child) throws IOException {
        if (child.isConflict()) {
            child.setParent(this);
            children.add(child);
            return child;
        }

        ASTNodeArtifact myChild;
        try {
            myChild = new ASTNodeArtifact((ASTNode<?>) child.astnode.clone());
            // migrathe the added and deleted flag
            if (child.isAdded()) {
                myChild.setAdded();
            }
            if (child.isDeleted()) {
                myChild.setDeleted();
            }
            myChild.deleteChildren();
            myChild.setRevision(child.getRevision());
            myChild.setParent(this);
            myChild.astnode.setParent(astnode);
            myChild.setRevision(child.getRevision());
            myChild.setNumber(child.getNumber());
            myChild.cloneMatches(child);

            if (children == null) {
                initializeChildren();
            }
            children.add(myChild);
            // astnode.flushCaches();
            if (LOG.isTraceEnabled()) {
                LOG.trace("Added child " + myChild.getId() + " to parent node " + getId());
            }
            return myChild;
        } catch (CloneNotSupportedException e) {
            throw new NotYetImplementedException();
        }

    }

    @Override
    public final int compareTo(final ASTNodeArtifact o) {
        if (hasUniqueLabels()) {
            return astnode.dumpString().compareTo(o.astnode.dumpString());
        } else {
            throw new RuntimeException("This artifact is not comparable.");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * de.fosd.jdime.common.Artifact#copyArtifact(de.fosd.jdime.common.Artifact)
     */
    @Override
    public final void copyArtifact(final ASTNodeArtifact destination) throws IOException {
        ASTNodeArtifact childCopy = destination.addChild(this);
        if (!isConflict() && hasChildren()) {
            for (ASTNodeArtifact child : getChildren()) {
                if (isAdded()) {
                    child.setAdded();
                }
                if (isDeleted()) {
                    child.setDeleted();
                }
                child.copyArtifact(childCopy);
            }
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see de.fosd.jdime.common.Artifact#createArtifact(boolean)
     */
    @Override
    public final void createArtifact(final boolean isLeaf) throws IOException {
        // TODO Auto-generated method stub
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#createEmptyDummy()
     */
    @Override
    public final ASTNodeArtifact createEmptyDummy() throws FileNotFoundException {
        ASTNodeArtifact dummy = new ASTNodeArtifact();
        dummy.astnode = new ASTNode<>();
        dummy.setEmptyDummy(true);
        dummy.setRevision(getRevision());
        return dummy;
    }

    /**
      *
      */
    public final void deleteChildren() {
        while (hasChildren()) {
            ASTNodeArtifact child = getChild(0);
            child.astnode = null;
            children.remove(0);
        }
    }

    /**
     * Returns the AST in dot-format.
     *
     * @param includeNumbers
     *            include node number in label if true
     * @return AST in dot-format.
     */
    public final String dumpGraphvizTree(final boolean includeNumbers) {
        assert (astnode != null);
        StringBuilder sb = new StringBuilder();
        sb.append(getNumber()).append("[label=\"");

        // node label
        if (includeNumbers) {
            sb.append("(").append(getNumber()).append(") ");
        }

        sb.append(astnode.dumpString());

        sb.append("\"");

        //      if (hasMatches()) {
        //         sb.append(", fillcolor = green, style = filled");
        //      }
        if (isConflict()) {
            // should not happen
            sb.append(", fillcolor = gray, style = filled");
        } else if (isAdded()) {
            sb.append(", fillcolor = yellow, style = filled");
        } else if (isDeleted()) {
            sb.append(", fillcolor = red, style = filled");
        }

        sb.append("];");
        sb.append(System.lineSeparator());

        // children
        for (ASTNodeArtifact child : getChildren()) {
            sb.append(child.dumpGraphvizTree(includeNumbers));

            // edge
            sb.append(getNumber()).append("->").append(child.getNumber()).append(";")
                    .append(System.lineSeparator());
        }

        return sb.toString();
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#dumpTree(java.lang.String)
     */
    @Override
    protected final String dumpTree(final String indent) {
        assert (astnode != null);
        StringBuilder sb = new StringBuilder();

        // node itself
        Matching<ASTNodeArtifact> m = null;

        // color
        if (!isConflict() && hasMatches()) {

            Set<Revision> matchingRevisions = matches.keySet();

            // print color code
            String color = "";

            for (Revision rev : matchingRevisions) {
                m = getMatching(rev);
                color = m.getColor().toShell();
            }

            sb.append(color);
        }

        if (isConflict()) {
            sb.append(Color.RED.toShell());
            sb.append(indent).append("(").append(getId()).append(") ");
            sb.append(this);
            sb.append(System.lineSeparator());
            sb.append(Color.RED.toShell());
            sb.append("<<<<<<< ");
            sb.append(System.lineSeparator());
            // children
            if (left != null) {
                sb.append(left.dumpTree(indent));
            }
            sb.append(Color.RED.toShell());
            sb.append("======= ");
            sb.append(System.lineSeparator());
            // children
            if (right != null) {
                sb.append(right.dumpTree(indent));
            }

            sb.append(Color.RED.toShell());
            sb.append(">>>>>>> ");
            sb.append(System.lineSeparator());
        } else {
            sb.append(indent).append("(").append(getId()).append(") ");
            sb.append(this);

            if (hasMatches()) {
                assert (m != null);
                sb.append(" <=> (").append(m.getMatchingArtifact(this).getId()).append(")");
                sb.append(Color.DEFAULT.toShell());
            }
            sb.append(System.lineSeparator());

            // children
            for (ASTNodeArtifact child : getChildren()) {
                sb.append(child.dumpTree(indent + "  "));
            }
        }

        return sb.toString();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    // @Override
    @Override
    public final boolean exists() {
        return astnode != null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#getId()
     */
    @Override
    public final String getId() {
        return getRevision() + ":" + getNumber();
    }

    @Override
    public final String getStatsKey(final MergeContext context) {
        return "nodes";
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#hashCode()
     */
    @Override
    public final int hashCode() {
        return astnode.dumpString().hashCode();
    }

    @Override
    public final boolean hasUniqueLabels() {
        return ImportDecl.class.isAssignableFrom(astnode.getClass())
                || Literal.class.isAssignableFrom(astnode.getClass());
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#initializeChildren()
     */
    @Override
    public final void initializeChildren() {
        assert (astnode != null);
        ArtifactList<ASTNodeArtifact> children = new ArtifactList<>();
        for (int i = 0; i < astnode.getNumChildNoTransform(); i++) {
            ASTNodeArtifact child = new ASTNodeArtifact(astnode.getChild(i));
            child.setParent(this);
            child.setRevision(getRevision());
            children.add(child);
        }
        setChildren(children);
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#isEmpty()
     */
    @Override
    public final boolean isEmpty() {
        return !hasChildren();
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#isLeaf()
     */
    @Override
    public final boolean isLeaf() {
        // TODO Auto-generated method stub
        return false;
    }

    /**
     * Returns whether declaration order is significant for this node.
     *
     * @return whether declaration order is significant for this node
     */
    @Override
    public final boolean isOrdered() {
        return !ConstructorDecl.class.isAssignableFrom(astnode.getClass())
                && !MethodDecl.class.isAssignableFrom(astnode.getClass())
                && !InterfaceDecl.class.isAssignableFrom(astnode.getClass())
                && !FieldDecl.class.isAssignableFrom(astnode.getClass())
                && !FieldDeclaration.class.isAssignableFrom(astnode.getClass())
                && !ImportDecl.class.isAssignableFrom(astnode.getClass());
    }

    /**
     * Returns whether a node matches another node.
     *
     * @param other
     *            node to compare with.
     * @return true if the node matches another node.
     */
    @Override
    public final boolean matches(final ASTNodeArtifact other) {
        assert (astnode != null);
        assert (other != null);
        assert (other.astnode != null);

        if (LOG.isDebugEnabled()) {
            LOG.debug("match(" + getId() + ", " + other.getId() + ")");
        }

        if ((ImportDecl.class.isAssignableFrom(astnode.getClass())
                || Literal.class.isAssignableFrom(astnode.getClass()))
                && other.astnode.getClass().equals(astnode.getClass())) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Try Matching (prettyPrint): {" + astnode.prettyPrint() + "} and {"
                        + other.astnode.prettyPrint() + "}");
            }
            return astnode.prettyPrint().equals(other.astnode.prettyPrint());
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Try Matching (dumpString): {" + astnode.dumpString() + "} and {" + other.astnode.dumpString()
                    + "}");
        }
        return astnode.dumpString().equals(other.astnode.dumpString());
    }

    @Override
    public final void merge(MergeOperation<ASTNodeArtifact> operation, MergeContext context)
            throws IOException, InterruptedException {
        Objects.requireNonNull(operation, "operation must not be null!");
        Objects.requireNonNull(context, "context must not be null!");

        MergeStrategy<ASTNodeArtifact> astNodeStrategy = new ASTNodeStrategy();

        if (LOG.isDebugEnabled()) {
            LOG.debug("Using strategy: " + astNodeStrategy);
        }

        MergeTriple<ASTNodeArtifact> triple = operation.getMergeTriple();
        ASTNodeArtifact left = triple.getLeft();
        ASTNodeArtifact right = triple.getRight();
        ASTNodeArtifact target = operation.getTarget();

        // Question: What is safemerge?
        boolean safeMerge = true;

        // Question: why does it matter?
        int numChildNoTransform;
        try {
            numChildNoTransform = target.astnode.getClass().newInstance().getNumChildNoTransform();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException();
        }

        // Question: what makes the root node different?
        // Question: what's the meaning of numChildNoTransform?
        if (!isRoot() && numChildNoTransform > 0) {

            // this language element has a fixed number of children, we need to be careful with this one
            boolean leftChanges = left.hasChanges(false);
            boolean rightChanges = right.hasChanges(false);

            // If both left and right have changes when compared to base, there is a conflict.
            if (leftChanges && rightChanges) {

                if (LOG.isTraceEnabled()) {
                    LOG.trace("Target " + target.getId() + " expects a fixed amount of children.");
                    LOG.trace("Both " + left.getId() + " and " + right.getId() + " contain changes.");
                    LOG.trace("We will report a conflict instead of performing the merge.");
                }
                //            safeMerge = false;
                //
                //            // to be safe, we will report a conflict instead of merging
                //            ASTNodeArtifact targetParent = target.getParent();
                //            targetParent.removeChild(target);
                //
                //            Operation<ASTNodeArtifact> conflictOp = new ConflictOperation<>(left, left, right, targetParent);
                //            conflictOp.apply(context);
            }
        }

        if (safeMerge) {
            astNodeStrategy.merge(operation, context);
        }

        if (!context.isQuiet() && context.hasOutput()) {
            System.out.print(context.getStdIn());
        }
    }

    /**
     * Removes a child.
     *
     * @param child
     *            child that should be removed
     */
    public final void removeChild(final ASTNodeArtifact child) {
        if (LOG.isTraceEnabled()) {
            LOG.trace("[" + getId() + "] removing child " + child.getId());
            LOG.trace("children before removal: " + getChildren());
        }

        Iterator<ASTNodeArtifact> it = children.iterator();
        ASTNodeArtifact elem;
        while (it.hasNext()) {
            elem = it.next();
            if (elem == child) {
                it.remove();
            }
        }

        if (LOG.isTraceEnabled()) {
            LOG.trace("children after removal: " + getChildren());
        }
    }

    /**
     * Pretty-prints the AST to source code.
     *
     * @return Pretty-printed AST (source code)
     */
    public final String prettyPrint() {
        assert (astnode != null);
        //        insertCondBoolean();
        //        expandConflict();
        rebuildAST();
        astnode.flushCaches();
        if (LOG.isDebugEnabled()) {
            System.out.println(dumpTree());
        }
        return astnode.prettyPrint();
    }

    /**
     * Rebuild the encapsulated ASTNode tree top down. This should be only
     * called at the root node
     */
    public final void rebuildAST() {

        if (isConflict()) {
            astnode.isConflict = true;
            astnode.jdimeId = getId();

            if (left != null) {
                left.rebuildAST();
                astnode.left = left.astnode;
            }

            if (right != null) {
                right.rebuildAST();
                astnode.right = right.astnode;
            }
        }

        ASTNode<?>[] newchildren = new ASTNode[getNumChildren()];

        for (int i = 0; i < getNumChildren(); i++) {
            ASTNodeArtifact child = getChild(i);
            newchildren[i] = child.astnode;
            newchildren[i].setParent(astnode);
            child.rebuildAST();
        }
        astnode.jdimeChanges = hasChanges();
        astnode.jdimeId = getId();
        astnode.setChildren(newchildren);

        //      assert (isConflict() || getNumChildren() == astnode
        //            .getNumChildNoTransform());
    }

    /*
     * (non-Javadoc)
     *
     * @see de.fosd.jdime.common.Artifact#toString()
     */
    @Override
    public final String toString() {
        assert (astnode != null);
        return astnode.dumpString();
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.fosd.jdime.common.Artifact#write(java.lang.String)
     */
    @Override
    public final void write(final String str) throws IOException {
        // TODO Auto-generated method stub
    }

    @Override
    public final ASTNodeArtifact createConflictDummy(final ASTNodeArtifact type, final ASTNodeArtifact left,
            final ASTNodeArtifact right) throws FileNotFoundException {

        ASTNodeArtifact conflict;

        conflict = new ASTNodeArtifact(type.astnode.fullCopy());
        conflict.setConflict(left, right);

        return conflict;
    }

    public boolean replaceChild(ASTNode<?> rootNode, ASTNode<?> origin, ASTNode<?> replace) {
        if (rootNode.equals(origin)) {
            ASTNode<?> parent = rootNode.getParent();
            int index = parent.getIndexOfChild(rootNode);
            parent.removeChild(index);
            parent.insertChild(replace, index);
            return true;
        }
        boolean success = false;
        for (int i = 0; i < rootNode.getNumChild(); i++) {
            success |= replaceChild(rootNode.getChild(i), origin, replace);
        }
        return success;
    }

    /**
    * Returns statistical data of the tree. stats[0]: number of nodes stats[1]:
    * tree depth stats[2]: maximum number of children
    *
    * @return statistics
    */
    public final int[] getStats() {
        // 0: number of nodes, 1: tree depth, 2: max children
        int[] mystats = new int[3];
        mystats[0] = 1;
        mystats[1] = 0;
        mystats[2] = getNumChildren();

        for (int i = 0; i < getNumChildren(); i++) {
            int[] childstats = getChild(i).getStats();
            mystats[0] += childstats[0];
            if (childstats[1] + 1 > mystats[1]) {
                mystats[1] = childstats[1] + 1;
            }
            if (childstats[2] > mystats[2]) {
                mystats[2] = childstats[2];
            }
        }

        return mystats;
    }

    public final ASTStats getStats(Revision revision, LangElem level, boolean isFragment) {
        StatsElement nodeStats = new StatsElement();
        StatsElement toplevelnodeStats = new StatsElement();
        StatsElement classlevelnodeStats = new StatsElement();
        StatsElement methodlevelnodeStats = new StatsElement();
        StatsElement classStats = new StatsElement();
        StatsElement methodStats = new StatsElement();

        // clearly, this is a node
        nodeStats.incrementElements();

        if (isConflict()) {
            nodeStats.incrementChanges();
            nodeStats.incrementConflicting();
        } else if ((revision == null && hasMatches()) || hasMatching(revision)) {
            nodeStats.incrementMatches();
        } else {
            nodeStats.incrementChanges();
            // added or deleted?
            if (hasMatches()) {
                // was deleted
                nodeStats.incrementDeleted();
            } else {
                // was added
                nodeStats.incrementAdded();
            }
        }

        StatsElement myStats = null;
        switch (level) {
        case TOPLEVELNODE:
            myStats = toplevelnodeStats;
            break;
        case CLASSLEVELNODE:
            myStats = classlevelnodeStats;
            break;
        case METHODLEVELNODE:
            myStats = methodlevelnodeStats;
            break;
        default:
            throw new NotYetImplementedException();
        }

        assert (myStats != null);

        nodeStats.copy(myStats);
        assert myStats.getElements() != 0;

        // find out level for child nodes and adjust class and method counter
        if (astnode instanceof ClassDecl) {
            level = LangElem.CLASSLEVELNODE;
            myStats.copy(classStats);
        } else if (astnode instanceof MethodDecl || astnode instanceof ConstructorDecl) {
            level = LangElem.METHODLEVELNODE;
            myStats.copy(methodStats);
        }

        HashMap<String, StatsElement> diffstats = new HashMap<>();
        diffstats.put(LangElem.NODE.toString(), nodeStats);
        diffstats.put(LangElem.TOPLEVELNODE.toString(), toplevelnodeStats);
        diffstats.put(LangElem.CLASSLEVELNODE.toString(), classlevelnodeStats);
        diffstats.put(LangElem.METHODLEVELNODE.toString(), methodlevelnodeStats);
        diffstats.put(LangElem.CLASS.toString(), classStats);
        diffstats.put(LangElem.METHOD.toString(), methodStats);
        ASTStats stats = new ASTStats(1, 1, getNumChildren(), diffstats, myStats.getChanges() != 0);
        boolean hasSubtreeChanges = stats.hasChanges();

        if (!hasSubtreeChanges) {
            isFragment = false;
        } else if (!isFragment) {
            isFragment = true;
            stats.incrementFragments();
        }

        boolean assertsEnabled = false;
        assert assertsEnabled = true;
        if (assertsEnabled) {
            for (String key : diffstats.keySet()) {
                StatsElement e = diffstats.get(key);
                assert (e.getElements() == e.getMatches() + e.getAdded() + e.getDeleted() + e.getConflicting());
                assert (e.getChanges() == e.getAdded() + e.getDeleted() + e.getConflicting());
            }

        }

        for (int i = 0; i < getNumChildren(); i++) {
            stats.add(getChild(i).getStats(revision, level, isFragment));

            if (!hasSubtreeChanges && stats.hasChanges()) {
                hasSubtreeChanges = true;
                if (astnode instanceof ClassDecl) {
                    stats.getDiffStats(LangElem.CLASS.toString()).incrementChanges();
                } else if (astnode instanceof MethodDecl || astnode instanceof ConstructorDecl) {
                    stats.getDiffStats(LangElem.METHOD.toString()).incrementChanges();
                }
            }

            if (assertsEnabled) {
                for (String key : diffstats.keySet()) {
                    StatsElement e = diffstats.get(key);
                    assert (e.getElements() == e.getMatches() + e.getAdded() + e.getDeleted() + e.getConflicting());
                }
            }
        }

        return stats;
    }
}