/*OsmUi is a user interface for Osmosis Copyright (C) 2011 Verena Käfer, Peter Vollmer, Niklas Schnelle This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.osmui.util; import java.text.ParseException; import java.util.EmptyStackException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import de.osmui.i18n.I18N; import de.osmui.model.exceptions.TasksNotCompatibleException; import de.osmui.model.exceptions.TasksNotInModelException; import de.osmui.model.pipelinemodel.AbstractPipe; import de.osmui.model.pipelinemodel.AbstractPipelineModel; import de.osmui.model.pipelinemodel.AbstractPort; import de.osmui.model.pipelinemodel.AbstractTask; import de.osmui.model.pipelinemodel.VariablePipe; import de.osmui.model.pipelinemodel.VariablePort; import de.osmui.model.pipelinemodel.parameters.AbstractParameter; import de.osmui.model.pipelinemodel.parameters.IntParameter; import de.osmui.util.exceptions.ImportException; import de.osmui.util.exceptions.TaskNameUnknownException; /** * This class is responsible for importing an Osmosis command line into an * AbstractPipelineModel and exporting an Osmosis command line from a given * AbstractPipelineModel. It is implemented using the Singelton pattern. * * @author Niklas Schnelle, Peter Vollmer, Verena Käfer * * @see CommandLineTraslatorTest */ public class CommandlineTranslator { protected static CommandlineTranslator instance; /** Private constructor for Singelton pattern **/ private CommandlineTranslator() { } /** * This method finishes a pipe, that is it connects its still unconnected * ports and adds its pipes to the stack, or if they are named map * * @param model * @param currTask * @param pipeStack * @param pipeMap * @throws ImportException */ private void finishTask(AbstractPipelineModel model, AbstractTask currTask, Stack<AbstractPipe> pipeStack, Map<String, AbstractPipe> pipeMap) throws ImportException { System.out.println(currTask.getCommandlineForm()); // Wire up input ports not connected through explicit naming using the // stack for (AbstractPort port : currTask.getInputPorts()) { if (!port.isConnected()) { try { model.connectTasks(pipeStack.pop(), port); } catch (TasksNotCompatibleException e) { throw new ImportException( I18N.getString("CommandlineTranslator.triedConnectIncompatibleTasks") + currTask.getCommandlineForm()); } catch (EmptyStackException e) { throw new ImportException( I18N.getString("CommandlineTranslator.noUnamedPipes")); } catch (TasksNotInModelException e) { // Failure of program logic task should be in the model e.printStackTrace(); } } } // Push all unnamed pipes onto the stack and add named pipes to the map for (AbstractPipe pipe : currTask.getOutputPipes()) { if (!pipe.isNamed()) { pipeStack.push(pipe); } else { pipeMap.put(pipe.getName(), pipe); // If the pipe name starts with "AUTO" we don't need this auto // generated name anymore pipe.setName(null); } } System.out.println("Task done"); } /** * This method creates a new task object from the given token * * @param tm * @param currToken * @return * @throws ImportException */ private AbstractTask createTaskFromToken(TaskManager tm, String currToken) throws ImportException { // Create a new task object String taskName = tm.unshortenTaskname(currToken.substring(2)); try { return tm.createTask(taskName); } catch (TaskNameUnknownException e) { throw new ImportException(I18N.getString( "CommandlineTranslator.taskDoesNotMatchAnything", taskName)); } } /** * This method handles parameters for the current task, especially useful * here is that for variable pipes their specifying parameter will be dealt * with. That is new pipes will be created. * * @param currTask * @param param * @param paramValue * @throws ImportException */ private void handleParam(AbstractTask currTask, AbstractParameter param, String paramValue) throws ImportException { // Check if the parameter specifies a variable // pipe/port count and create pipes/ports // accordingly if (param instanceof IntParameter) { IntParameter intParam = (IntParameter) param; if(intParam.isReferenced()){ int wantedCount = Integer.parseInt(paramValue); int defaultCount = Integer.parseInt(intParam.getDefaultValue()); for (AbstractPort port : currTask.getInputPorts()) { if (port instanceof VariablePort && ((VariablePort) port).getReferencedParam().equals( intParam)) { AbstractPort newPort; for (int i = defaultCount; i < wantedCount; ++i) { newPort = ((VariablePort) port).createPort(); currTask.getInputPorts().add(newPort); } // We are done return; } } for (AbstractPipe pipe : currTask.getOutputPipes()) { if (pipe instanceof VariablePipe && ((VariablePipe) pipe).getReferencedParam().equals( intParam)) { AbstractPipe newPipe; for (int i = defaultCount; i < wantedCount; ++i) { newPipe = ((VariablePipe) pipe).createPipe(); currTask.getOutputPipes().add(newPipe); } // We are done return; } } } } try { param.setValue(paramValue); } catch (NumberFormatException e) { throw new ImportException(I18N.getString( "CommandlineTranslator.paramExpecNum", param.getName())); } } /** * This method handles Tokens that are either a pipe or a parameter * * @param model * the model we are importing to * @param currTask * the current task * @param pipeMap * the map where named pipes are stored * @param currToken * the current token * @throws ImportException */ private void handleParamOrPipe(AbstractPipelineModel model, AbstractTask currTask, Map<String, AbstractPipe> pipeMap, String currToken) throws ImportException { AbstractParameter param; AbstractPipe pipe; String paramName; String paramValue; int pipeNum = 0; int eqIndex = currToken.indexOf("="); paramName = (eqIndex >= 0) ? currToken.substring(0, eqIndex) : null; paramValue = (eqIndex >= 0) ? currToken.substring(eqIndex + 1) : currToken; if (paramName == null) { // This is the value of the default parameter param = currTask.getDefaultParameter(); handleParam(currTask, param, paramValue); } else if (paramName.startsWith("inPipe.")) { // We need to connect a named pipe with the correct port // 7th position is the one after the . pipeNum = Integer.parseInt(paramName.substring(7)); // Check if the pipeNum exists if (pipeNum >= currTask.getInputPorts().size()) { throw new ImportException(I18N.getString( "CommandlineTranslatorPipe.indexInPipeNotExist", currToken)); } pipe = pipeMap.remove(paramValue); if (pipe == null) { throw new ImportException(I18N.getString( "CommandlineTranslator.unknownPipe", paramValue)); } try { model.connectTasks(pipe, currTask.getInputPorts().get(pipeNum)); } catch (TasksNotCompatibleException e) { throw new ImportException(I18N.getString( "CommandlineTranslatorTried.incompatibleTask", currTask.getCommandlineForm())); } catch (TasksNotInModelException e) { // Failure in program logic task should be added e.printStackTrace(); } } else if (paramName.startsWith("outPipe.")) { // We got a new named pipe // 6th position is the one after the . pipeNum = Integer.parseInt(paramName.substring(8)); // Check if the pipeNum exists if (pipeNum >= currTask.getOutputPipes().size()) { throw new ImportException( I18N.getString( "CommandlineTranslator.indexOutPipeNotExist", currToken)); } currTask.getOutputPipes().get(pipeNum).setName(paramValue); } else { // This is a named parameter param = currTask.getParameters().get(paramName); // Maybe this parameter is a TagFilterParameter where // we can't know the name if (param == null) { // Check if the task has a filter, which can have the form "foo=bar" and therefore is treated // like a parameter where it actually is it's value param = currTask.getParameters().get("tagfilter"); paramValue=currToken; } if (param == null){ throw new ImportException(I18N.getString( "CommandlineTranslator.unknownParameter", paramName, currTask.getName())); } handleParam(currTask, param, paramValue); } } /** * Imports an osmosis command line into the given model. This is an example * line: "--rx full/planet-071128.osm.bz2 " + "--tee 2 outPipe.1=fooPipe " + * "--bp file=polygons/europe/germany/baden-wuerttemberg.poly \\" + * "--wx baden-wuerttemberg.osm.bz2 inPipe.0=fooPipe \\" + * "--bp file=polygons/europe/germany/bayern.poly " + "--wx bayern.osm.bz2"" * * @param model * @param line * @throws ImportException * @throws ParseException */ public void importLine(AbstractPipelineModel model, String line, char escapeChar) throws ImportException, ParseException { //Scanner st = new Scanner(line); //st.useDelimiter("[ \\t\\r\\n\\f\\\\]+"); char[] quoteChars = {'\'', '"'}; CommandlineSplitter st = new CommandlineSplitter(line, quoteChars , escapeChar); // Stack for unnamed pipes Stack<AbstractPipe> pipeStack = new Stack<AbstractPipe>(); // HashMap for named pipes so we can connect them later on HashMap<String, AbstractPipe> pipeMap = new HashMap<String, AbstractPipe>(); AbstractTask currTask = null; TaskManager tm = TaskManager.getInstance(); String currToken; while (st.hasNext()) { currToken = st.next(); if (currToken.startsWith("--")) { // Ok we got a new task: // If currTask != null this wasn't the first task and the last // is done if (currTask != null) { finishTask(model, currTask, pipeStack, pipeMap); } currTask = createTaskFromToken(tm, currToken); model.addTask(currTask); } else { // Not a task must be parameter or pipe, the currTask must be // non null or else something's wrong if (currTask == null) { throw new ImportException( I18N.getString("CommandlineTranslator.noParamBeforeFirstTask")); } handleParamOrPipe(model, currTask, pipeMap, currToken); } } // Finish the last Task if (currTask != null) { finishTask(model, currTask, pipeStack, pipeMap); } } /** * This private function is used to deal with an unfinished task: - It adds * all still unfinished Downstream tasks that aren't yet in unfinished to it * - It tries whether all dependencies are met, if not pushing them - When * called again (after being added by a now finished upstream task) it marks * this task as finished and writes it to the StringBuilder - This algorithm * ensures that tasks will (if possible) be put right before their * downstream neighbor * * @param unfin * @param fin * @param sb * @param task * @param lineSep * the line separator e.g. "\\\n" for .sh use "" for single line */ private void exportTask(Stack<AbstractTask> unfin, Set<AbstractTask> fin, StringBuilder sb, AbstractTask task, String lineSep) { // When we are done we need the downstream tasks on the stack AbstractTask currTask; AbstractPort downPort; // Push all connected unfinished Downstream tasks for (AbstractPipe pipe : task.getOutputPipes()) { if (pipe.isConnected()) { downPort = pipe.getTarget(); currTask = downPort.getParent(); if (!fin.contains(currTask) && !unfin.contains(currTask)) { unfin.push(currTask); } } } // Push any unfinished dependencies AbstractPipe upPipe; boolean unmetDependecy = false; for (AbstractPort port : task.getInputPorts()) { if (port.isConnected()) { upPipe = port.getIncoming(); currTask = upPipe.getSource(); if (!fin.contains(currTask) && !unfin.contains(currTask)) { unfin.push(currTask); unmetDependecy = true; } } } if (unmetDependecy) { return; } // All dependencies are now cleared append task (without pipes) sb.append(task.getCommandlineForm()); sb.append(lineSep); // This task is now finished fin.add(task); } /** * This method exports a model into a osmosis call e.g. * * @param model * @return the line e.g. * "--foo opt outPipe.0=AUTO1to1 --bar opt=val inPipe.0=AUTO1to1" */ public String exportLine(AbstractPipelineModel model, String lineSep) { Stack<AbstractTask> unfinished = new Stack<AbstractTask>(); HashSet<AbstractTask> finished = new HashSet<AbstractTask>(); StringBuilder builder = new StringBuilder(); // Add all source tasks to the unfinished stack for (AbstractTask task : model.getSourceTasks()) { unfinished.add(task); } while (!unfinished.isEmpty()) { AbstractTask currTask = unfinished.pop(); if (!finished.contains(currTask)) { // This call tries to finish the task, if it still needs // dependencies // it will push those to resolve them first exportTask(unfinished, finished, builder, currTask, lineSep); } } return builder.toString(); } /** * This returns an Instance of CommandlineTranslator, see Singelton pattern * * @return */ public static CommandlineTranslator getInstance() { if (instance == null) { instance = new CommandlineTranslator(); } return instance; } }