Java tutorial
/******************************************************************************* * Copyright (c) 2014 * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Benjamin Klatt *******************************************************************************/ package org.splevo.jamopp.vpm.mergedecider; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeMap; import org.apache.log4j.Logger; import org.emftext.language.java.commons.Commentable; import org.emftext.language.java.containers.CompilationUnit; import org.emftext.language.java.members.Member; import org.emftext.language.java.members.Method; import org.emftext.language.java.statements.Statement; import org.emftext.language.java.types.Type; import org.splevo.jamopp.util.JaMoPPElementUtil; import org.splevo.jamopp.vpm.software.JaMoPPJavaSoftwareElement; import org.splevo.vpm.analyzer.mergedecider.MergeDecider; import org.splevo.vpm.software.SoftwareElement; import org.splevo.vpm.variability.Variant; import org.splevo.vpm.variability.VariationPoint; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * JaMoPP specific component to decide about the merge-ability for two variation points. */ public class JaMoPPMergeDecider implements MergeDecider { private static Logger logger = Logger.getLogger(JaMoPPMergeDecider.class); @Override public boolean canBeMerged(VariationPoint vp1, VariationPoint vp2) { SoftwareElement location1 = vp1.getLocation(); SoftwareElement location2 = vp2.getLocation(); Boolean bothJaMoPPElements = checkThatBothJaMoPPElements(location1, location2); if (!bothJaMoPPElements) { return false; } Commentable jamoppLocation1 = ((JaMoPPJavaSoftwareElement) location1).getJamoppElement(); Commentable jamoppLocation2 = ((JaMoPPJavaSoftwareElement) location2).getJamoppElement(); if (jamoppLocation1 != jamoppLocation2) { return false; } if (checkDistinctVariants(vp1, vp2)) { return true; } if (checkMergeableImports(vp1, vp2)) { return true; } if (checkMergeableStatements(vp1, vp2)) { return true; } return false; } /** * Check if the variation points are about changed statements which are located in their parent * container in a way that they can be combined. * * This check assumes that variation points location is already checked to be the same. * * @param vp1 * The first variation point to check. * @param vp2 * The second variation point to check. * @return */ private boolean checkMergeableStatements(VariationPoint vp1, VariationPoint vp2) { Commentable loc1 = getJaMoPPLocation(vp1); Commentable loc2 = getJaMoPPLocation(vp2); // Not only statements but also nested statement container must be handled if (!(loc1 instanceof Method) || !(loc2 instanceof Method)) { return false; } ArrayListMultimap<String, Statement> variantStatements = ArrayListMultimap.create(); variantStatements.putAll(collectVariantStatements(vp1)); variantStatements.putAll(collectVariantStatements(vp2)); // only if more than one statement exists for at least one variant, // the algorithm can make a decision if the VPs can be merged or not. // Otherwise the result is false or undecidable (so better false) boolean variantsHaveOnlyStatement = true; for (String variantId : variantStatements.keySet()) { List<Statement> statements = variantStatements.get(variantId); if (statements.size() > 1) { variantsHaveOnlyStatement = false; } TreeMap<Integer, Statement> positionIndex = indexStatementPosition(statements); Iterator<Integer> keyIterator = positionIndex.keySet().iterator(); int lastPosition = -1; while (keyIterator.hasNext()) { int currentPosition = keyIterator.next(); // DesignDecision: Check statements position only and no program dependencies if (!statementIsFirstOrDirectFollower(lastPosition, currentPosition)) { return false; } lastPosition = currentPosition; } } return !variantsHaveOnlyStatement; } private TreeMap<Integer, Statement> indexStatementPosition(List<Statement> statements) { TreeMap<Integer, Statement> positionIndex = Maps.newTreeMap(); for (Statement statement : statements) { int pos = JaMoPPElementUtil.getPositionInContainer(statement); positionIndex.put(pos, statement); } return positionIndex; } private boolean statementIsFirstOrDirectFollower(int lastPosition, int position) { return lastPosition == -1 || Math.abs(lastPosition - position) == 1; } /** * Collect all implementing statement elements per variant of a VariationPoint. * * @param vp * The variation point to investigate. * @return A map associating all variants to their implementing statements. */ private ArrayListMultimap<String, Statement> collectVariantStatements(VariationPoint vp) { ArrayListMultimap<String, Statement> variantStatements = ArrayListMultimap.create(); for (Variant variant : vp.getVariants()) { for (SoftwareElement se : variant.getImplementingElements()) { JaMoPPJavaSoftwareElement jamoppSoftwareElement = (JaMoPPJavaSoftwareElement) se; Commentable jamoppElement = jamoppSoftwareElement.getJamoppElement(); if (!(jamoppElement instanceof Statement)) { logger.warn("No statement: " + jamoppElement.getClass().getSimpleName()); } Statement statement = (Statement) jamoppElement; variantStatements.get(variant.getId()).add(statement); } } return variantStatements; } /** * True if the variation points have distinct variants only. This is only valid for elements not * aware about their order. * * This check assumes that the VPs location has already be proven to be the same. * * @param vp1 * The first variation point to check * @param vp2 * The second variation point to check * @return True if both VPs have contributions for different variants only. */ // TODO: Check if it's valid to assume distinct variants for two vps is sufficient to merge them private boolean checkDistinctVariants(VariationPoint vp1, VariationPoint vp2) { if (hasOrderIndepentElementsOnly(vp1) && hasOrderIndepentElementsOnly(vp2)) { Set<String> vp1Ids = getVariantIds(vp1); Set<String> vp2Ids = getVariantIds(vp2); return Collections.disjoint(vp1Ids, vp2Ids); } else { return false; } } private boolean hasOrderIndepentElementsOnly(VariationPoint vp) { for (Variant v : vp.getVariants()) { for (SoftwareElement element : v.getImplementingElements()) { if (element instanceof JaMoPPJavaSoftwareElement) { JaMoPPJavaSoftwareElement jamoppElement = (JaMoPPJavaSoftwareElement) element; if (!isOrderIndependent(jamoppElement.getJamoppElement())) { return false; } } else { // if it is not a jamopp element it could not be decided // so better return false return false; } } } return true; } private boolean isOrderIndependent(Commentable jamoppElement) { return (jamoppElement instanceof Type) || (jamoppElement instanceof CompilationUnit) || (jamoppElement instanceof Member); } /** * Get the set of ids for the variants of variation point. * * @param vp * The variation point. * @return The list of it's ids. */ private Set<String> getVariantIds(VariationPoint vp) { Set<String> vpIds = Sets.newLinkedHashSet(); for (Variant variant : vp.getVariants()) { vpIds.add(variant.getId()); } return vpIds; } private boolean checkMergeableImports(VariationPoint vp1, VariationPoint vp2) { Commentable loc1 = getJaMoPPLocation(vp1); Commentable loc2 = getJaMoPPLocation(vp2); if (!(loc1 instanceof CompilationUnit) || !(loc2 instanceof CompilationUnit)) { return false; } return true; } /** * Get the JaMoPP Element representing the location of the variation point. If this element can * not be determined return null. * * @param vp * The variation point to get the location element for. * @return The JaMoPP Element or null if not available. */ private Commentable getJaMoPPLocation(VariationPoint vp) { JaMoPPJavaSoftwareElement element = (JaMoPPJavaSoftwareElement) vp.getLocation(); return element.getJamoppElement(); } /** * Check if two elements are JaMoPP ones.<br> * If they are: Return True<br> * If only one or none: Return False<br> * * @param elem1 * First element to check. * @param elem2 * Second element to check. * @return True/False according to the rules above. */ private boolean checkThatBothJaMoPPElements(SoftwareElement elem1, SoftwareElement elem2) { boolean isJaMoPP1 = isJaMoPPSoftwareElement(elem1); boolean isJaMoPP2 = isJaMoPPSoftwareElement(elem2); return (isJaMoPP1 && isJaMoPP2); } private boolean isJaMoPPSoftwareElement(SoftwareElement softwareElement) { return (softwareElement instanceof JaMoPPJavaSoftwareElement); } }