/* Copyright (C) 2006 Christian Schneider * * This file is part of Nomad. * * Nomad 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 2 of the License, or * (at your option) any later version. * * Nomad 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 Nomad; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.sf.nmedit.jpatch.transform; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.nmedit.jpatch.ModuleDescriptions; import net.sf.nmedit.jpatch.PConnectorDescriptor; import net.sf.nmedit.jpatch.PModuleDescriptor; import net.sf.nmedit.jpatch.PParameterDescriptor; import net.sf.nmedit.jpatch.PRuntimeException; /** * Automat to build the structure as specified in the * <a href="../../../../../../html/transform/transform.html#toc4." >Transformations XML v1.1</a> document format. * * Each element (except the root element) of the XML Format has one or two corresponding methods in this class: * * <ul> * <li><code><group></code>: {@link #beginGroup()}</li> * <li><code></group></code>: {@link #endGroup()}</li> * <li><code><module></code>: {@link #beginModule(String)}</li> * <li><code></module></code>: {@link #endModule()}</li> * <li><code><parameter ... /></code>: {@link #parameter(String, String)}</li> * <li><code><connector ... ></code>: {@link #connector(String, String)}</li> * </ul> * * @author Christian Schneider */ public class PTBuilder { private ModuleDescriptions md; private boolean done = false; private PTModuleSelector currentModule; private List<PTModuleSelector> currentGroup; private List<List<PTModuleSelector>> groups; private Map<String, Integer> selectorIdMap; private Map<String, Integer> selectorTypeMap; private int selectorIdCounter = Integer.MIN_VALUE; private Integer TYPE_INPUT = 1; private Integer TYPE_OUTPUT = 1; private Integer TYPE_PARAMETER = 3; private PTModuleSelector[] merged; /** * Stores the component ids of the modules in the current group. * Used to find out if a module is declared multiple times which is an error. */ private Set<Object> modules ; /** * Stores the component ids of the parameters of the current module. * Used to find out if a parameter is declared multiple times which is an error. */ private Set<Object> parameters ; /** * Stores the component ids of the connectors of the current module. * Used to find out if a connector is declared multiple times which is an error. */ private Set<Object> connectors ; /** * An automat for building the XML Transformation v1.1 document structure. * @param moduleDescriptions the module descriptors */ public PTBuilder(ModuleDescriptions moduleDescriptions) { this.md = moduleDescriptions; selectorIdMap = new HashMap<String, Integer>(); selectorTypeMap = new HashMap<String, Integer>(); groups = new ArrayList<List<PTModuleSelector>>(); modules = new HashSet<Object>(); parameters = new HashSet<Object>(); connectors = new HashSet<Object>(); } /** * Begins a new group. * For each call to beginGroup() a call to {@link #endGroup()} must follow * after the modules were added to the group. * @throws PRuntimeException if {@link #done()} was called before or * a previous group was not completed by {@link #endGroup()}. */ public void beginGroup() { ensureNotDone(); if (currentGroup != null) throw new PRuntimeException("previous group not completed"); selectorIdMap.clear(); selectorTypeMap.clear(); modules.clear(); currentGroup = new ArrayList<PTModuleSelector>(); } /** * Completes the group which was started using {@link #beginGroup()}. * @throws PRuntimeException if {@link #done()} was called before or * no group was created by a previous call to {@link #beginGroup()}. */ public void endGroup() { ensureNotDone(); if (currentGroup == null) throw new PRuntimeException("no group selected"); if (currentGroup.size()>1) { fixSelectorIds(currentGroup); // at least two modules must be present groups.add(currentGroup); } currentGroup = null; } /** * Adjusts the selector ids in the specified group. Afterwards * if a component from this group and a component from the previous * groups have the same selector id, then these components can * be mapped to each other. * * @param group the group in which the selector ids are adjusted */ private void fixSelectorIds(List<PTModuleSelector> group) { Map<Integer, Integer> fixedIds = new HashMap<Integer, Integer>(group.size()*2); for (PTModuleSelector module: group) { fixSelectorIds(fixedIds, module); } // set new id's for (PTModuleSelector module: group) { boolean fixed = false; for (PTSelector sel: module) { Integer newId = fixedIds.get(sel.getSelectorId()); if (newId != null) { sel.setSelectorId(newId.intValue()); fixed = true; } } if (fixed) module.rebuildMap(); } } private void fixSelectorIds(Map<Integer, Integer> fixedIds, PTModuleSelector module) { final Object mid = module.getDescriptor().getComponentId(); for (List<PTModuleSelector> group: groups) { for (PTModuleSelector mreference: group) { if (mreference.getDescriptor().getComponentId().equals(mid)) { // same module for (PTSelector refsel: mreference) { Object refid = refsel.getDescriptor().getComponentId(); for (PTSelector compsel: module) { if (refid.equals(compsel.getDescriptor().getComponentId())) { if (compsel.getSelectorId() != refsel.getSelectorId()) fixedIds.put(compsel.getSelectorId(), refsel.getSelectorId()); } } } } } } } /** * Begins a new module. * The specified componentId is used to lookup the module in the module descriptions. * * {@link #beginGroup()} must have been called before. * * @param componentId the component id if the module * @throws PRuntimeException if {@link #done()} was called before, * {@link #beginGroup()} was not called before or if the module * with the specified component id does not exist. */ public void beginModule(String componentId) { ensureNotDone(); if (currentModule != null) throw new PRuntimeException("nested modules are not supported"); PModuleDescriptor descriptor = md.getModuleById(componentId); if (descriptor == null) throw new PRuntimeException("module[id="+componentId+"] does not exist"); if (modules.contains(descriptor.getComponentId())) throw new PRuntimeException("ambigious module "+descriptor); parameters.clear(); connectors.clear(); currentModule = new PTModuleSelector(descriptor); modules.add(descriptor.getComponentId()); } /** * Completes the current module. {@link #beginModule(String)} must have been called before. * @throws PRuntimeException if {@link #done()} was called before or * {@link #beginModule(String)} was not called before. */ public void endModule() { ensureNotDone(); ensureModulePresent(); currentGroup.add(currentModule); currentModule = null; } /** * Adds the connector to the current module element. The componentId is used to * lookup the connector descriptor in the currently selected module. * * @param componentId the connector id * @param selectorName selector * @throws PRuntimeException if {@link #beginModule(String)} was not called before, * {@link #done()} was called before or the connector with the specified componentId does not exist. */ public void connector(String componentId, String selectorName) { ensureNotDone(); ensureModulePresent(); PConnectorDescriptor descriptor = currentModule.getDescriptor().getConnectorByComponentId(componentId); if (descriptor == null) throw new PRuntimeException("connector[id="+componentId+"] does not exist in "+currentModule); // ensure descriptor is only defined once if (connectors.contains(descriptor.getComponentId())) throw new PRuntimeException("ambigious connector "+descriptor); connectors.add(descriptor.getComponentId()); int selectorId = getSelectorId(selectorName, descriptor.isOutput() ? TYPE_OUTPUT : TYPE_INPUT); currentModule.add(new PTSelector(descriptor, selectorId)); } /** * Adds the parameter to the current module element. The componentId is used to * lookup the parameter descriptor in the currently selected module. * * @param componentId the parameter id * @param selectorName selector * @throws PRuntimeException if {@link #beginModule(String)} was not called before, * {@link #done()} was called before or the parameter with the specified componentId does not exist. */ public void parameter(String componentId, String selectorName) { ensureNotDone(); ensureModulePresent(); PParameterDescriptor descriptor = currentModule.getDescriptor().getParameterByComponentId(componentId); if (descriptor == null) throw new PRuntimeException("parameter[id="+componentId+"] does not exist in "+currentModule); // ensure descriptor is only defined once if (parameters.contains(descriptor.getComponentId())) throw new PRuntimeException("ambigious parameter "+descriptor); parameters.add(descriptor.getComponentId()); int selectorId = getSelectorId(selectorName, TYPE_PARAMETER); currentModule.add(new PTSelector(descriptor, selectorId)); } /** * Ensures that {@link #beginModule(String)} was called but not completed * by {@link #endModule()}. * @throws PRuntimeException if the current module is null */ private void ensureModulePresent() { if (currentModule == null) throw new PRuntimeException("no module selected"); } /** * Ensures that {@link #done()} was not called before. * @throws PRuntimeException if done() was called before */ private void ensureNotDone() { if (done) throw new PRuntimeException("can not add more elements"); } /** * Returns the id for the specified selector. * The argument type is used to ensure that no connector has the same selector as a parameter. * The argument type can take one of the values * <ul> * <li>{@link #TYPE_INPUT}</li> * <li>{@link #TYPE_OUTPUT}</li> * <li>{@link #TYPE_PARAMETER}</li> * </ul> * * @param selectorName name of the selector * @param type type of the selected component (parameter or connector) * @return the selector id * @throws PRuntimeException if the selector name is used by connectors and parameters */ private int getSelectorId(String selectorName, Integer type) { int selectorId; // get the selector id Integer selected = selectorIdMap.get(selectorName); // see if the selector id is already defined, otherwise create a new id if (selected != null) { // id is already defined => now ensure that parameters and connectors do not have the same id if (!type.equals(selectorTypeMap.get(selectorName))) throw new PRuntimeException("parameter/input/output have the same selector name:"+selectorName+", "+currentModule); // set the id selectorId = selected.intValue(); } else { // create a new id selectorId = selectorIdCounter++; // store the id for the selector name selectorIdMap.put(selectorName, selectorId); // store the type of the selector (connector or parameter) selectorTypeMap.put(selectorName, type); } // return the selector id return selectorId; } /** * Completes the document. This must be called before {@link #getGroups()} can be called. * * The first time the method is called the groups are tested to ensure that the * defined module combinations are not ambiguous (two groups must not contain same pairs of modules). * * @throws PRuntimeException If the latest call to * {@link #beginGroup()} was not completed by {@link #endGroup()}. */ public void done() { if (currentGroup != null) throw new PRuntimeException("current group not completed"); if (done) return; validateGroups(); merged = merge(groups); done = true; } /** * Returns the defined module mappings. * * @return the defined module mappings. * @throws PRuntimeException {@link #done()} was not called before */ public PTModuleSelector[] getSelectors() { if (!done) throw new PRuntimeException("groups are not completed"); return merged; } /** * Ensures that two different groups do not contain the same module pairs. * More formally ensures that the condition |intersection(g1, g2)|<2 is * true. */ protected void validateGroups() { // groups g1, g2, g1!=g2 // modules m1, m2, m1!=m2 // intersection({m1,m2}, g1) != intersection({m1,m2}, g2) // // thus if m1 and m2 are both in g1 then // both can not be in g2 at the same time // // this implies that |intersection(g1, g2)|<2 // for each group g1, g2, g1!=g2: final int groupCount = groups.size(); for (int i=0;i<groupCount-2;i++) { List<PTModuleSelector> g1 = groups.get(i); for (int j=i+1;j<groupCount-1;j++) { List<PTModuleSelector> g2 = groups.get(j); // count number of elements in intersection int intersection = 0; for (PTModuleSelector m: g1) { if (contains(g2, m)) { // => (m in g1) and (m in g2) if (intersection>=2) { // condition is violated throw new PRuntimeException("ambigious module "+m.getDescriptor() +"/condition violated: '|{m : m in g1, m in g2|<2'"); } } } } } } public static PTModuleSelector[] merge(List<List<PTModuleSelector>> groups) { List<PTModuleSelector> modules = new ArrayList<PTModuleSelector>(groups.size()*2); // add all module selectors for (List<PTModuleSelector> group: groups) modules.addAll(group); // merge module selectors with the same module descriptor for (int i=0;i<modules.size()-1;i++) { PTModuleSelector a = modules.get(i); for (int j=i+1;j<modules.size();) { PTModuleSelector b = modules.get(j); if (a.getDescriptor().getComponentId().equals(b.getDescriptor().getComponentId())) { // remove b modules.remove(j); // a <- union (a,b) for (PTSelector s: b) if (!a.containsKey(s.getSelectorId())) a.add(s); continue; // do not increment j because element was removed } j++; } } return modules.toArray(new PTModuleSelector[modules.size()]); } /** * Returns true if the module is already in the group. * @param group set of module selectors * @param m the module * @return true if the module is already in the group */ private boolean contains(List<PTModuleSelector> group, PTModuleSelector m) { for (PTModuleSelector b: group) { if (b.getDescriptor().getComponentId().equals(m.getDescriptor().getComponentId())) return true; } return false; } }