/** * Copyright (c) 2012-2016 Marsha Chechik, Alessio Di Sandro, Michalis Famelis, * Rick Salay. * 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: * Alessio Di Sandro - Implementation. */ package edu.toronto.cs.se.mmint.mid.operator; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.jdt.annotation.NonNull; import edu.toronto.cs.se.mmint.MMINT; import edu.toronto.cs.se.mmint.MMINTConstants; import edu.toronto.cs.se.mmint.MMINTException; import edu.toronto.cs.se.mmint.MIDTypeRegistry; import edu.toronto.cs.se.mmint.mid.GenericElement; import edu.toronto.cs.se.mmint.mid.MID; import edu.toronto.cs.se.mmint.mid.Model; import edu.toronto.cs.se.mmint.mid.ModelEndpoint; import edu.toronto.cs.se.mmint.mid.editor.Editor; import edu.toronto.cs.se.mmint.mid.operator.Operator; import edu.toronto.cs.se.mmint.mid.operator.OperatorInput; import edu.toronto.cs.se.mmint.mid.operator.impl.OperatorImpl; import edu.toronto.cs.se.mmint.mid.relationship.ModelRel; import edu.toronto.cs.se.mmint.mid.utils.FileUtils; import edu.toronto.cs.se.mmint.mid.utils.MIDOperatorIOUtils; public class Reduce extends OperatorImpl { // input-output private final static @NonNull String IN_MID = "mid"; private final static @NonNull String OUT_MID = "reducedMid"; private final static @NonNull String GENERIC_OPERATORTYPE = "ACCUMULATOR"; // constants private final static @NonNull String REDUCED_MID_SUFFIX = "_reduce"; private final static @NonNull String MODELRELCOMPOSITION_OPERATORTYPE_URI = "http://se.cs.toronto.edu/mmint/Operator_ModelRelComposition"; private final static @NonNull String MODELRELMERGE_OPERATORTYPE_URI = "http://se.cs.toronto.edu/mmint/Operator_ModelRelMerge"; @Override public boolean isAllowedGeneric(GenericEndpoint genericTypeEndpoint, GenericElement genericType, EList<OperatorInput> inputs) throws MMINTException { boolean allowed = super.isAllowedGeneric(genericTypeEndpoint, genericType, inputs); if (!allowed) { return false; } if (genericType.getName().equals("Filter") || genericType.getName().equals("Map") || genericType.getName().equals("Reduce")) { return false; } return true; } private @NonNull MID reduce(@NonNull Model inputMIDModel, @NonNull Operator accumulatorOperatorType) throws Exception { // preparation for accumulator operator MID reducedMID = (MID) inputMIDModel.getEMFInstanceRoot(); Map<String, MID> accumulatorOutputMIDsByName = MIDOperatorIOUtils .createSimpleOutputMIDsByName(accumulatorOperatorType, reducedMID); EList<MID> inputMIDs = new BasicEList<>(); inputMIDs.add(reducedMID); Set<Model> accumulatorOutputModelsToDelete = new HashSet<>(); Map<String, Model> accumulatorOutputsByName = null; EList<OperatorInput> accumulatorInputs; // preparation for composition operator Operator compositionOperatorType = MIDTypeRegistry.getType(MODELRELCOMPOSITION_OPERATORTYPE_URI); Map<String, MID> compositionOutputMIDsByName = MIDOperatorIOUtils .createSimpleOutputMIDsByName(compositionOperatorType, reducedMID); // preparation for merge operator Operator mergeOperatorType = MIDTypeRegistry.getType(MODELRELMERGE_OPERATORTYPE_URI); Map<String, MID> mergeOutputMIDsByName = MIDOperatorIOUtils .createSimpleOutputMIDsByName(mergeOperatorType, reducedMID); // reduce loop while ((accumulatorInputs = accumulatorOperatorType.findFirstAllowedInput(inputMIDs)) != null) { Set<Model> accumulatorInputModels = new HashSet<>(); Set<ModelRel> accumulatorInputModelRels = new HashSet<>(); try { // get all model inputs, including the ones attached to model rel inputs for (OperatorInput accumulatorInput : accumulatorInputs) { Model accumulatorInputModel = accumulatorInput.getModel(); if (accumulatorInputModel instanceof ModelRel) { accumulatorInputModels.addAll(((ModelRel) accumulatorInputModel).getModelEndpoints().stream() .map(ModelEndpoint::getTarget) .collect(Collectors.toSet())); accumulatorInputModelRels.add((ModelRel) accumulatorInputModel); } /** * <!-- begin-user-doc --> Starts an instance of this operator type, i.e. runs conversions for the input models, * creates an operator instance, invokes {@link #readInputProperties(Properties)}, {@link #init()}, * {@link #run(Map, Map, Map)}, records the execution time. * * @param inputs * A list of inputs to run the operator instance, including necessary conversions. * @param inputProperties * The input properties, or null to read them from a file named OperatorNameIn.properties. * @param generics * A list of generics to run the operator instance. * @param outputMIDsByName * The instance MIDs where the output models are created, identified by the output name. A null instance * MID means that the output model isn't added to it. * @param instanceMID * The Instance MID where the operator instance is run, null if the operator isn't going to be added to * it. * @return The executed operator instance. * @throws Exception * If something went wrong starting the operator. <!-- end-user-doc --> * @model required="true" exceptions="edu.toronto.cs.se.mmint.mid.operator.Exception" inputsMany="true" * genericsMany="true" outputMIDsByNameRequired="true" instanceMIDRequired="true" * @generated */ else { accumulatorInputModels.add(accumulatorInputModel); } } // get all model rels attached to input models that are not inputs themselves //TODO MMINT[OO] This is expensive, need a direct way to reach model rels from models Set<ModelRel> connectedModelRels = reducedMID.getModelRels().stream() .filter(modelRel -> !accumulatorInputModelRels.contains(modelRel)) .filter(modelRel -> !Collections.disjoint( accumulatorInputModels, modelRel.getModelEndpoints().stream() .map(ModelEndpoint::getTarget) .collect(Collectors.toSet()))) .collect(Collectors.toSet()); // run the ACCUMULATOR operator EList<OperatorGeneric> accumulatorGenerics = accumulatorOperatorType.selectAllowedGenerics( accumulatorInputs); accumulatorOutputsByName = accumulatorOperatorType.startInstance( accumulatorInputs, null, accumulatorGenerics, accumulatorOutputMIDsByName, null) .getOutputsByName(); accumulatorOutputModelsToDelete.addAll( accumulatorOutputsByName.values().stream() .filter(accumulatorOutputModel -> !(accumulatorOutputModel instanceof ModelRel)) .collect(Collectors.toSet())); // for each model rel in the output that is connected with the input models, do the composition List<ModelRel> composedModelRels = new ArrayList<>(); for (ModelRel connectedModelRel : connectedModelRels) { for (Model accumulatorModelRel : accumulatorOutputsByName.values()) { if (!(accumulatorModelRel instanceof ModelRel)) { continue; } try { EList<Model> compositionInputModels = new BasicEList<>(); compositionInputModels.add(connectedModelRel); compositionInputModels.add(accumulatorModelRel); EList<OperatorInput> compositionInputs = compositionOperatorType.checkAllowedInputs( compositionInputModels); if (compositionInputs == null) { continue; } Map<String, Model> compositionOutputsByName = compositionOperatorType.startInstance( compositionInputs, null, new BasicEList<>(), compositionOutputMIDsByName, null) .getOutputsByName(); composedModelRels.add( (ModelRel) compositionOutputsByName.get( compositionOperatorType.getOutputs().get(0).getName())); } catch (Exception e) { MMINTException.print( IStatus.WARNING, "Operator " + compositionOperatorType + " execution error, skipping it", e); } } } // merge model rels that have been composed and share the same model endpoints Set<ModelRel> composedModelRelsToDelete = new HashSet<>(); for (int i = 0; i < composedModelRels.size(); i++) { ModelRel composedModelRel1 = composedModelRels.get(i); for (int j = i+1; j < composedModelRels.size(); j++) { ModelRel composedModelRel2 = composedModelRels.get(j); try { EList<Model> mergeInputModels = new BasicEList<>(); mergeInputModels.add(composedModelRel1); mergeInputModels.add(composedModelRel2); EList<OperatorInput> mergeInputs = mergeOperatorType.checkAllowedInputs( mergeInputModels); if (mergeInputs == null) { continue; } mergeOperatorType.startInstance(mergeInputs, null, new BasicEList<>(), mergeOutputMIDsByName, null); composedModelRelsToDelete.add(composedModelRel1); composedModelRelsToDelete.add(composedModelRel2); } catch (Exception e) { MMINTException.print( IStatus.WARNING, "Operator " + mergeOperatorType + " execution error, skipping it", e); } } } composedModelRelsToDelete.forEach(compModelRelToDelete -> { try { compModelRelToDelete.deleteInstance(); } catch (MMINTException e) {} }); } catch (Exception e) { MMINTException.print( IStatus.WARNING, "Operator " + accumulatorOperatorType + " execution error, skipping it", e); } finally { // delete accumulator inputs (model rels are deleted as a side effect of deleting the models) // do it in case of failure too, because it will trigger an endless loop otherwise accumulatorInputModels.forEach(accumulatorInputModel -> { try { accumulatorInputModel.deleteInstance(); } catch (MMINTException e) {} }); } } // delete intermediate output model files but the ones from last execution for (Model accumulatorOutputModelToDelete : accumulatorOutputModelsToDelete) { if (accumulatorOutputsByName != null && accumulatorOutputsByName.values().contains(accumulatorOutputModelToDelete)) { continue; } for (Editor accumulatorOutputEditorToDelete : accumulatorOutputModelToDelete.getEditors()) { FileUtils.deleteFile(accumulatorOutputEditorToDelete.getUri(), true); } FileUtils.deleteFile(accumulatorOutputModelToDelete.getUri(), true); } return reducedMID; } @Override public Map<String, Model> run( Map<String, Model> inputsByName, Map<String, GenericElement> genericsByName, Map<String, MID> outputMIDsByName) throws Exception { // input Model inputMIDModel = inputsByName.get(IN_MID); Operator accumulatorOperatorType = (Operator) genericsByName.get(GENERIC_OPERATORTYPE); MID instanceMID = outputMIDsByName.get(OUT_MID); // loop until reduction is no longer possible, reducing one input at a time boolean openEditors = Boolean.parseBoolean( MMINT.getPreference(MMINTConstants.PREFERENCE_MENU_OPENMODELEDITORS_ENABLED)); if (openEditors) { MMINT.setPreference(MMINTConstants.PREFERENCE_MENU_OPENMODELEDITORS_ENABLED, "false"); } MID reducedMID = reduce(inputMIDModel, accumulatorOperatorType); if (openEditors) { MMINT.setPreference(MMINTConstants.PREFERENCE_MENU_OPENMODELEDITORS_ENABLED, "true"); } // output String reducedMIDModelUri = FileUtils.getUniqueUri( FileUtils.addFileNameSuffixInUri(inputMIDModel.getUri(), REDUCED_MID_SUFFIX), true, false); FileUtils.writeModelFile(reducedMID, reducedMIDModelUri, true); Model midModelType = MIDTypeRegistry.getMIDModelType(); Model reducedMIDModel = midModelType.createInstanceAndEditor(reducedMIDModelUri, instanceMID); Map<String, Model> outputsByName = new HashMap<>(); outputsByName.put(OUT_MID, reducedMIDModel); return outputsByName; } }