/******************************************************************************* * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package hr.fer.zemris.vhdllab.applets.editor.schema2.model.commands; import hr.fer.zemris.vhdllab.applets.editor.schema2.enums.EErrorTypes; import hr.fer.zemris.vhdllab.applets.editor.schema2.enums.EPropertyChange; import hr.fer.zemris.vhdllab.applets.editor.schema2.exceptions.DuplicateKeyException; import hr.fer.zemris.vhdllab.applets.editor.schema2.exceptions.InvalidCommandOperationException; import hr.fer.zemris.vhdllab.applets.editor.schema2.exceptions.OverlapException; import hr.fer.zemris.vhdllab.applets.editor.schema2.exceptions.UnknownKeyException; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ICommand; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ICommandResponse; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.IQueryResult; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaComponent; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaComponentCollection; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaInfo; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaWire; import hr.fer.zemris.vhdllab.applets.editor.schema2.interfaces.ISchemaWireCollection; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.AutoRenamer; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.Caseless; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.ChangeTuple; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.IntList; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.PlacedComponent; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.SchemaError; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.SchemaPort; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.WireSegment; import hr.fer.zemris.vhdllab.applets.editor.schema2.misc.XYLocation; import hr.fer.zemris.vhdllab.applets.editor.schema2.model.CommandResponse; import hr.fer.zemris.vhdllab.applets.editor.schema2.model.SchemaWire; import hr.fer.zemris.vhdllab.applets.editor.schema2.model.queries.FindDisjointSegments; import hr.fer.zemris.vhdllab.applets.editor.schema2.model.queries.FindDisjointSegments.DisjointSets; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Map.Entry; /** * Brise jedan segment odredene zice i u slucaju * da nakon brisanja tog segmenta zice bude podijeljena * u vise cjelina, obavlja podjelu zice na dvije nove. * Ako ova komanda obrise posljednji segment zice, bit * ce obrisana i citava zica. * Ako se obrise segment zice u doticaju s komponentom * u koju je ista zica ukopcana, * zica ce automatski biti iskopcana iz pripadnog porta * komponente. * * @author brijest * */ public class DeleteSegmentAndDivideCommand implements ICommand { private static class UnplugWrapper { public Caseless compname; public int schemaportindex; public UnplugWrapper(Caseless cmpname, int index) { compname = cmpname; schemaportindex = index; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof UnplugWrapper)) return false; UnplugWrapper other = (UnplugWrapper)obj; return other.compname.equals(this.compname) && other.schemaportindex == this.schemaportindex; } @Override public int hashCode() { return compname.hashCode() << 16 + schemaportindex; } } private static enum EActionState { normal() { @Override public ICommandResponse undoHousework(ISchemaInfo info, DeleteSegmentAndDivideCommand command) { // do absolutely nothing return null; } }, deleted() { @Override public ICommandResponse undoHousework(ISchemaInfo info, DeleteSegmentAndDivideCommand command) { SchemaWire wire = new SchemaWire(command.wirename); wire.insertSegment(command.segment); try { info.getWires().addWire(wire); } catch (DuplicateKeyException e) { throw new IllegalStateException("Duplicate key problems when performing undo.", e); } catch (OverlapException e) { throw new IllegalStateException("Overlap problems when performing undo.", e); } return null; } }, divided() { @Override public ICommandResponse undoHousework(ISchemaInfo info, DeleteSegmentAndDivideCommand command) { ISchemaComponentCollection components = info.getComponents(); ISchemaWireCollection wires = info.getWires(); ISchemaWire wire = wires.fetchWire(command.wirename); // remap components to old wire for (Entry<Caseless, IntList> ntry : command.renamedmappings.entrySet()) { ISchemaComponent cmp = components.fetchComponent(ntry.getKey()); IntList lst = ntry.getValue(); for (int i = 0, sz = lst.size(); i < sz; i++) { cmp.getSchemaPort(lst.get(i)).setMapping(command.wirename); } } // for each name in divided set for (Caseless divname : command.divided) { ISchemaWire divwire = wires.fetchWire(divname); if (divwire == null) throw new IllegalStateException("Could not find wire from division."); if (wire == null) throw new IllegalStateException("Could not find wire '" + command.wirename + "'."); // put all segments from the divided wire to the original wire for (WireSegment ws : divwire.getSegments()) { wire.insertSegment(ws); } // remove divided wire try { wires.removeWire(divname); } catch (UnknownKeyException e) { throw new IllegalStateException("Could not remove wire '" + divname + "' that exists.", e); } } // assign null to divided set, as nothing divided exists command.divided = null; return null; } }; /** * * @return * Vraca null ako je sve dobro proslo. */ public abstract ICommandResponse undoHousework(ISchemaInfo info, DeleteSegmentAndDivideCommand command); } /* static fields */ public static final String COMMAND_NAME = "DeleteSegmentAndDivideCommand"; /* private fields */ protected Caseless wirename; protected WireSegment segment; protected Set<Caseless> divided; private Set<UnplugWrapper> unplugged; protected Map<Caseless, IntList> renamedmappings; private EActionState actionstate; /* ctors */ public DeleteSegmentAndDivideCommand(Caseless wireToEditName, WireSegment segmentToDelete) { wirename = wireToEditName; segment = segmentToDelete; } /* methods */ public String getCommandName() { return COMMAND_NAME; } public boolean isUndoable() { return true; } public ICommandResponse performCommand(ISchemaInfo info) { ISchemaWireCollection wires = info.getWires(); ISchemaWire wire = wires.fetchWire(wirename); if (wire == null) return new CommandResponse(new SchemaError(EErrorTypes.NONEXISTING_WIRE_NAME, "Wire '" + wirename + "' does not exist.")); // remove segment if (!wire.removeSegment(segment)) { return new CommandResponse(new SchemaError(EErrorTypes.NONEXISTING_SEGMENT, "'" + segment.toString() + "' not among segments belonging to wire '" + wirename + "'.")); } actionstate = EActionState.normal; // now check if this was the last segment, and delete wire if necessary if (wire.getSegments().size() == 0) { try { wires.removeWire(wirename); } catch (UnknownKeyException e) { throw new IllegalStateException("Wire '" + wirename + "' cannot be removed, " + "yet it has been found.", e); } actionstate = EActionState.deleted; } else { // divide wire into 2 or more wires if necessary FindDisjointSegments findsegs = new FindDisjointSegments(wirename); IQueryResult result = findsegs.performQuery(info); if (!result.isSuccessful()) { wire.insertSegment(segment); return new CommandResponse(new SchemaError(EErrorTypes.INNER_QUERY_NOT_PERFORMED, "Could not analyze disjoint sets to find potential wire divisions.")); } divided = new HashSet<Caseless>(); DisjointSets dissets = (DisjointSets)result.get(FindDisjointSegments.KEY_SEGMENT_SETS); for (int i = 1, sz = dissets.segmentsets.size(); i < sz; i++) { // create a new wire Caseless divname = AutoRenamer.getFreeName(wirename, wires.getWireNames()); divided.add(divname); ISchemaWire nwire = new SchemaWire(divname); // transform wire segments to the new wire for (WireSegment ws : dissets.segmentsets.get(i)) { wire.removeSegment(ws); nwire.insertSegment(ws); } // put new wire to wire collection try { wires.addWire(nwire); } catch (DuplicateKeyException e) { throw new IllegalStateException("Autorenamer approved the name, " + "yet name seems to already exist.", e); } catch (OverlapException e) { throw new IllegalStateException("Overlapping problems with the new wire, " + "and they didn't seem to exist with the old wire.", e); } // find components mapped to oldwire and remap them to new wire renamedmappings = new HashMap<Caseless, IntList>(); for (PlacedComponent plc : info.getComponents()) { IntList changedports = null; int count = -1; for (SchemaPort sp : plc.comp.getSchemaPorts()) { count++; Caseless mappedto = sp.getMapping(); if (Caseless.isNullOrEmpty(mappedto)) continue; /* check if port was mapped to old name */ if (mappedto.equals(wirename)) { /* check if wire has segments at the position */ XYLocation sploc = sp.getOffset(); if (nwire.segmentsAt(plc.pos.x + sploc.x, plc.pos.y + sploc.y) != null) { /* set new mapping */ sp.setMapping(divname); /* cache mapped port for undo */ if (changedports == null) changedports = new IntList(); changedports.add(count); } } } if (changedports != null) { renamedmappings.put(plc.comp.getName(), changedports); } } actionstate = EActionState.divided; } } // check if segment was plugged into any component and unplug wire if necessary ISchemaComponentCollection components = info.getComponents(); XYLocation start = segment.getStart(), end = segment.getEnd(); unplugged = new HashSet<UnplugWrapper>(); unplugAt(start, components); unplugAt(end, components); return new CommandResponse(new ChangeTuple(EPropertyChange.CANVAS_CHANGE)); } private void unplugAt(XYLocation loc, ISchemaComponentCollection components) { ISchemaComponent cmp = components.fetchComponent(loc.x, loc.y, 1); if (cmp == null) return; if (cmp.isInvalidated()) return; XYLocation cloc = components.getComponentLocation(cmp.getName()); int count = 0; for (SchemaPort sp : cmp.getSchemaPorts()) { XYLocation portloc = sp.getOffset(); if ((cloc.x + portloc.x) == loc.x && (cloc.y + portloc.y) == loc.y) { if (!wirename.equals(sp.getMapping())) continue; sp.setMapping(null); unplugged.add(new UnplugWrapper(cmp.getName(), count)); } count++; } } public ICommandResponse undoCommand(ISchemaInfo info) throws InvalidCommandOperationException { // plug wire back into any component it may have been plugged into ISchemaComponentCollection components = info.getComponents(); for (UnplugWrapper uw : unplugged) { ISchemaComponent cmp = components.fetchComponent(uw.compname); cmp.getSchemaPort(uw.schemaportindex).setMapping(wirename); } // perform housework ICommandResponse resp = actionstate.undoHousework(info, this); if (resp != null && !resp.isSuccessful()) return resp; // fetch wire and insert the segment back ISchemaWire wire = info.getWires().fetchWire(wirename); if (wire == null) return new CommandResponse(new SchemaError(EErrorTypes.NONEXISTING_WIRE_NAME, "Wire '" + wirename + "' does not exist.")); wire.insertSegment(segment); return new CommandResponse(new ChangeTuple(EPropertyChange.CANVAS_CHANGE)); } }