/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.core.scripts.execution; import java.util.HashSet; import java.util.Iterator; import com.opendoorlogistics.api.ExecutionReport; import com.opendoorlogistics.core.scripts.elements.InstructionConfig; import com.opendoorlogistics.core.scripts.elements.Option; import com.opendoorlogistics.core.scripts.elements.Script; import com.opendoorlogistics.core.scripts.io.ScriptIO; import com.opendoorlogistics.core.scripts.utils.ScriptUtils; import com.opendoorlogistics.core.scripts.utils.TableId; import com.opendoorlogistics.core.utils.strings.StandardisedStringSet; import com.opendoorlogistics.core.utils.strings.Strings; public class OptionsSubpath { private OptionsSubpath() { } public static Script getSubpathScript(final Script script, final TableId[] adapterTables, final String[] adapterIds, ExecutionReport report) { try { // get option ids for the tables HashSet<String> ids = new HashSet<>(); for (TableId tableId : adapterTables) { ids.add(ScriptUtils.getOptionIdByAdapterId(script, tableId.getDsId())); } // and for the adapters for (String id : adapterIds) { String optionId = ScriptUtils.getOptionIdByAdapterId(script, id); if (optionId == null) { throw new RuntimeException("Data adapter not found in script: " + id); } ids.add(optionId); } // get the subpath Processor processor = new Processor(script, ids.toArray(new String[ids.size()]), report); processor.validateIds(); processor.trim(); // // trim any leaf option adapters not needed BEFORE collapsing the script // ScriptUtils.visitOptions(processor.script, new OptionVisitor() { // // @Override // public void visitOption(Option parent, Option option) { // if (option.getOptions().size() > 0) { // // non-leaf ... must keep // return; // } // // // loop over all adapters // Iterator<AdapterConfig> itAdapt = option.getAdapters().iterator(); // while (itAdapt.hasNext()) { // boolean keepAdapter = false; // // // is this adapter included in our list of adapters? // AdapterConfig currentAdapter = itAdapt.next(); // for (AdapterConfig included : adapters) { // if (Strings.equalsStd(currentAdapter.getId(), included.getId())) { // keepAdapter = true; // break; // } // } // // // should we keep some tables in the adapter? // if (!keepAdapter) { // // // Get the individual tables used actually within this adapter. // // Remove any tables not needed in the adapter. // List<AdaptedTableConfig> tables = tablesByAdapterId.get(currentAdapter.getId()); // if (tables != null) { // Iterator<AdaptedTableConfig> itTable = currentAdapter.getTables().iterator(); // boolean foundTable = false; // while (itTable.hasNext()) { // AdaptedTableConfig currentTable = itTable.next(); // for (AdaptedTableConfig other : tables) { // if (Strings.equalsStd(currentTable.getName(), other.getName())) { // foundTable = true; // } // } // // if (!foundTable) { // itTable.remove(); // } // } // } else { // currentAdapter.getTables().clear(); // } // // keepAdapter = currentAdapter.getTables().size() > 0; // } // // // remove adapter if unneeded // if (!keepAdapter) { // itAdapt.remove(); // } // } // } // // }); if(processor.finish()){ return processor.script; } return null; } catch (Exception e) { report.setFailed("Failed to generate script including only adapted tables needed."); return null; } } /** * For the input option ids or instruction ids, generate a script which executes only them by trimming the other options and placing all remaining * options at the root level. If ids are null then everything is put on a single level The synchronisation of the script is always worked out. * * If this method fails then the execution report is set to failed status. No exceptions are thrown. * * @param script * @param optionIds * @return */ public static Script getSubpathScript(Script script, final String[] optionIds, ExecutionReport report) { try { Processor processor = new Processor(script, optionIds, report); if(!processor.processAll()){ return null; } return processor.script; } catch (Exception e) { if(report!=null){ report.setFailed(e); } return null; } } // private static class SyncCounter { // int sync = 0; // int noSync = 0; // // SyncCounter(Script script, final String[] optionIds) { // ScriptUtils.visitOptions(script, new OptionVisitor() { // // @Override // public void visitOption(Option parent, Option option) { // // only examine leaf nodes; only these are counted as 'runnable' // if (option.getOptions().size() > 0) { // return; // } // // // is this option include in the id list? (null list means include all) // boolean included = false; // if (optionIds == null) { // included = true; // } else { // for (String optionId : optionIds) { // if (Strings.equalsStd(optionId, option.getOptionId())) { // included = true; // break; // } // } // } // if (!included) { // return; // } // // // check user was allowed to choose sync or not sync for this option // ArrayList<Option> tmpPath = new ArrayList<>(); // tmpPath.add(option); // OutputWindowSyncLevel sync = ScriptUtils.getOutputWindowSyncLevel(tmpPath); // if (sync == OutputWindowSyncLevel.ERROR) { // throw new RuntimeException("Option with id " + option.getOptionId() + " in the script is corrupt."); // } else if (sync != OutputWindowSyncLevel.MANUAL) { // return; // } // // // count it // count(option); // } // // }); // } // // private void count(Option option) { // if (option.isSynchronised()) { // sync++; // } else { // noSync++; // } // } // } private static class Processor{ StandardisedStringSet knownOptionIds = new StandardisedStringSet(false); StandardisedStringSet knownInstructionIds = new StandardisedStringSet(false); final Script script; final String []optionIds; final ExecutionReport report; Processor(Script script, final String[] optionIds, ExecutionReport report){ // take a deep copy of the input script this.script = ScriptIO.instance().deepCopy(script); this.optionIds = optionIds; this.report = report; } void validateIds(){ // get and validate ids in the script (TO DO ... include adapters etc...) knownOptionIds = new StandardisedStringSet(false); validateOptionIds(script, knownOptionIds, true); knownInstructionIds = new StandardisedStringSet(false); validateInstructionIds(script, knownInstructionIds); // Check all options exist if (optionIds != null) { for (String id : optionIds) { if (knownOptionIds.contains(id) == false) { throw new RuntimeException("Unknown script option id: " + id); } } } } void trim(){ // now get the trimmed script... if options is null we take the whole script... if (optionIds != null) { // now trim ... i.e. remove all unused options OptionsSubpath.trim(script, optionIds); } } boolean finish(){ // now collapse all options to one level collapseScriptToSingleOption(script, script); // if(!setSyncLevel(script, counter, report)){ // return false; // } return true; } boolean processAll(){ validateIds(); trim(); return finish(); } } // /** // * @param collapsedScript // * @param counter // * @param report // */ // private static boolean setSyncLevel(Script collapsedScript, SyncCounter counter, ExecutionReport report) { // // check the sync level // OutputWindowSyncLevel syncLevel = ScriptUtils.getOutputWindowSyncLevel(collapsedScript, collapsedScript.getOptionId()); // switch (syncLevel) { // case ERROR: // report.setFailed("The script is corrupt. It may contain incompatible components - one which can only be run synchronised and another unsynchronised."); // return false; // // case ALWAYS: // collapsedScript.setSynchronised(true); // break; // // case NEVER: // collapsedScript.setSynchronised(false); // break; // // case MANUAL: // // only sync if all set to sync... // collapsedScript.setSynchronised(counter.sync > 0 && counter.noSync == 0); // break; // } // return true; // } private static void collapseScriptToSingleOption(Script root, Option option) { if (option != root) { // add everything from this level root.getAdapters().addAll(option.getAdapters()); root.getComponentConfigs().addAll(option.getComponentConfigs()); root.getInstructions().addAll(option.getInstructions()); root.getOutputs().addAll(option.getOutputs()); } // add all its children, removing them as we go Iterator<Option> it = option.getOptions().iterator(); while (it.hasNext()) { collapseScriptToSingleOption(root, it.next()); it.remove(); } } private static boolean containsId(Option option, String id) { if (Strings.equalsStd(option.getOptionId(), id)) { return true; } for (Option child : option.getOptions()) { if (containsId(child, id)) { return true; } } return false; } private static void trim(Option option, String[] ids) { Iterator<Option> it = option.getOptions().iterator(); while (it.hasNext()) { Option child = it.next(); // trim child first trim(child, ids); // then check whether to keep this node boolean keep = false; for (String id : ids) { if (containsId(child, id)) { keep = true; break; } } if (!keep) { it.remove(); } } } private static void validateInstructionIds(Option option, StandardisedStringSet knownIds) { for (InstructionConfig instruction : option.getInstructions()) { String id = instruction.getUuid(); if (Strings.isEmpty(id)) { throw new RuntimeException("Corrupt script - empty instruction id."); } if (knownIds.contains(id)) { throw new RuntimeException("Corrupt script - duplication option-id: " + id); } knownIds.add(id); } for (Option child : option.getOptions()) { validateInstructionIds(child, knownIds); } } private static void validateOptionIds(Option option, StandardisedStringSet knownIds, boolean emptyAllowed) { if (Strings.isEmpty(option.getOptionId()) && emptyAllowed == false) { throw new RuntimeException("Corrupt script - empty option id."); } if (knownIds.contains(option.getOptionId())) { throw new RuntimeException("Corrupt script - duplication option-id: " + option.getOptionId()); } knownIds.add(option.getOptionId()); for (Option child : option.getOptions()) { validateOptionIds(child, knownIds, false); } } }