/******************************************************************************* * PSHDL is a library and (trans-)compiler for PSHDL input. It generates * output suitable for implementation or simulation of it. * * Copyright (C) 2013 Karsten Becker (feedback (at) pshdl (dot) org) * * 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 * (at your option) 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/>. * * This License does not grant permission to use the trade names, trademarks, * service marks, or product names of the Licensor, except as required for * reasonable and customary use in describing the origin of the Work. * * Contributors: * Karsten Becker - initial API and implementation ******************************************************************************/ package org.pshdl.model.utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.pshdl.model.IHDLObject; import org.pshdl.model.utils.HDLQuery.HDLFieldAccess; import org.pshdl.model.utils.HDLQuery.HDLFieldAccess.Quantifier; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * A {@link ModificationSet} records changes that will be made to the AST * (HDL*)-models. Those changes can then be applied to rewrite a completly new * AST. * * @author Karsten Becker * */ public class ModificationSet { private class MSCopyFilter extends CopyFilter.DeepCloneFilter { @SuppressWarnings("unchecked") @Override public <T extends IHDLObject> T copyObject(String feature, IHDLObject container, T original) { if (original == null) return null; final List<Modification> mods = getModifications(original); if (mods != null) { for (final Modification modification : mods) { if (modification.type == ModificationType.REPLACE) { if (modification.with.size() > 1) throw new IllegalArgumentException("Can not replace with more than one object into a single node feature:" + feature + " of " + container.getClassType()); if (currentMods.contains(modification.modificationID)) { continue; } final IHDLObject replacement = (IHDLObject) modification.with.get(0); final T actualReplacement; currentMods.add(modification.modificationID); actualReplacement = (T) copyObject(feature, container, replacement); currentMods.remove(modification.modificationID); return actualReplacement; } if (modification.type == ModificationType.REMOVE) return null; throw new IllegalArgumentException("Can not use " + modification.type + " for single node feature:" + feature + " of " + container.getClassType()); } } return super.copyObject(feature, container, original); } @Override public <T> ArrayList<T> copyContainer(String feature, IHDLObject container, Iterable<T> object) { final ArrayList<T> res = Lists.newArrayList(); if (object != null) { for (final T t : object) { applyModifications(feature, container, res, t); } doAddFeature(res, feature, container); return res; } doAddFeature(res, feature, container); if (!res.isEmpty()) return res; return null; } private <T> void applyModifications(String feature, IHDLObject container, final ArrayList<T> res, final T t) { final List<Modification> mods = getModifications(t); if (mods != null) { final List<T> before = Lists.newLinkedList(); final List<T> after = Lists.newLinkedList(); final List<T> replace = Lists.newLinkedList(); boolean remove = false; final Set<Integer> appliedModIDs = Sets.newHashSet(); for (final Modification modification : mods) { if (currentMods.contains(modification.modificationID)) { continue; } switch (modification.type) { case INSERT_AFTER: scheduleMod(after, appliedModIDs, modification); break; case INSERT_BEFORE: scheduleMod(before, appliedModIDs, modification); break; case REPLACE: scheduleMod(replace, appliedModIDs, modification); break; case REMOVE: remove = true; break; case ADD: break; } } multiAdd(res, before, container, feature); if (!replace.isEmpty()) { // A replacement is scheduled, so we just add the // replacement objects // and ignore the actual element multiAdd(res, replace, container, feature); } else { // The object is not scheduled for removal, so just add it if (!remove) { singleAdd(res, t, container, feature, true); } } multiAdd(res, after, container, feature); currentMods.removeAll(appliedModIDs); } else { // If there are no modifications for object t then we just add // it to the result singleAdd(res, t, container, feature, true); } } @SuppressWarnings("unchecked") private <T> void scheduleMod(final List<T> list, final Set<Integer> modIDs, final Modification modification) { modIDs.add(modification.modificationID); currentMods.add(modification.modificationID); list.addAll((Collection<? extends T>) modification.with); } /** * Performs all the {@link ModificationType#ADD} modifications for the * container and the feature. * * @param resultList * the list that will contain all the replacements * @param feature * the feature of the container that will contain the obj * @param container * the object that will contain the obj */ @SuppressWarnings("unchecked") private <T> void doAddFeature(final ArrayList<T> resultList, String feature, IHDLObject container) { final List<Modification> mods = getModifications(container); if (mods == null) return; final Set<Integer> appliableMods = Sets.newHashSet(); final List<Modification> applyMod = Lists.newArrayList(); for (final Modification mod : mods) { if ((mod.type == ModificationType.ADD) && feature.equals(mod.feature)) { if (currentMods.contains(mod.modificationID)) { continue; } appliableMods.add(mod.modificationID); applyMod.add(mod); } } currentMods.addAll(appliableMods); for (final Modification mod : applyMod) { multiAdd(resultList, (List<T>) mod.with, container, feature); } currentMods.removeAll(appliableMods); } /** * Adds multiple items to the result list <code>resultList</code> which * will be added to the <code>feature</code> of the * <code>container</code> * * @param resultList * the list that will contain all the replacements * @param objectList * the objects that should be inserted * @param container * the object that will contain the objects * @param feature * the feature of the container that will contain the objects */ private <T> void multiAdd(ArrayList<T> resultList, List<T> objectList, IHDLObject container, String feature) { for (final T element : objectList) { singleAdd(resultList, element, container, feature, false); } } /** * Adds a single item to the result list <code>resultList</code> which * will be added to the <code>feature</code> of the * <code>container</code> * * @param resultList * the list that will contain all the replacements * @param obj * the object that should be inserted * @param container * the object that will contain the obj * @param feature * the feature of the container that will contain the obj * @param unmodified * if <code>true</code> no further substitution will be * attempted */ @SuppressWarnings("unchecked") private <T> void singleAdd(ArrayList<T> resultList, T obj, IHDLObject container, String feature, boolean unmodified) { if (obj instanceof IHDLObject) { if (unmodified) { resultList.add((T) ((IHDLObject) obj).copyFiltered(this)); } else { resultList.addAll(copyContainer(feature, container, Collections.singleton(obj))); } } else { resultList.add(obj); } } } private static enum ModificationType { REPLACE, INSERT_BEFORE, INSERT_AFTER, ADD, REMOVE; } /** * A storage class for all modifications that are scheduled * */ private static class Modification { private static final AtomicInteger gid = new AtomicInteger(); public final Integer modificationID = gid.incrementAndGet(); public final IHDLObject subject; public final String feature; public final List<Object> with; public final ModificationType type; @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("Modification [type="); builder.append(type); builder.append(", subject="); builder.append(subject); builder.append(", feature="); builder.append(feature); builder.append(", with="); builder.append(with); builder.append(", modificationID="); builder.append(modificationID); builder.append("]"); return builder.toString(); } public Modification(IHDLObject subject, ModificationType type, String feature, Object... with) { super(); this.subject = subject; this.with = Arrays.asList(with); this.type = type; this.feature = feature; } } private final Map<IHDLObject, List<Modification>> replacements = Maps.newIdentityHashMap(); /** * Contains the modifications that are currently applied. A modification * that is currently applied, should not be applied to avoid recursion */ private final Set<Integer> currentMods = Sets.newHashSet(); private <T> List<Modification> getModifications(T object) { return replacements.get(object); } /** * Removes a subject * * @param subject */ public void remove(IHDLObject subject) { final IHDLObject container = subject.getContainer(); if (container != null) { final HDLFieldAccess<?, ?> feature = container.getContainingFeature(subject); // The feature can be null when the container is set // aritifically for resolution reasons if (feature != null) { switch (feature.quantifier) { case ONE: case ONE_OR_MORE: throw new IllegalArgumentException("Can not remove feature:" + feature.fieldName + " at least one is required"); default: break; } } } final Modification mod = new Modification(subject, ModificationType.REMOVE, null); insert(subject, mod); } /** * Replace the subject with other objects. If the with is null or empty, the * subject will be removed. * * @param subject * @param with */ public void replace(IHDLObject subject, IHDLObject... with) { if ((with == null) || (with.length == 0)) { remove(subject); } else { final IHDLObject container = subject.getContainer(); if (container != null) { final HDLFieldAccess<?, ?> feature = container.getContainingFeature(subject); // The feature can be null when the container is set // aritifically for resolution reasons if (feature != null) { switch (feature.quantifier) { case ONE: case ZERO_OR_ONE: if (with.length != 1) throw new IllegalArgumentException("Can not replace feature:" + feature.fieldName + " with multiple objects"); break; default: break; } } } final Modification mod = new Modification(subject, ModificationType.REPLACE, null, (Object[]) with); insert(subject, mod); } } /** * Inserts the objects after the subject. This only works if the subject is * contained in a List. * * @param subject * @param with */ public void insertAfter(IHDLObject subject, IHDLObject... with) { if ((with == null) || (with.length == 0)) return; final IHDLObject container = subject.getContainer(); if (container != null) { final HDLFieldAccess<?, ?> feature = container.getContainingFeature(subject); if (feature != null) { if ((feature.quantifier == Quantifier.ZERO_OR_ONE) || (feature.quantifier == Quantifier.ONE)) throw new IllegalArgumentException("Can not perform insertAfter on feature: " + feature.fieldName + " not a collection"); for (final IHDLObject object : with) { if (!feature.type.isAssignableFrom(object.getClass())) throw new IllegalArgumentException("Can not insertAfter type:" + object.getClassType() + " to feature: " + feature.fieldName); } } } final Modification mod = new Modification(subject, ModificationType.INSERT_AFTER, null, (Object[]) with); insert(subject, mod); } /** * Insert objects before the subject. This only works if the subject is * contained in a List. * * @param subject * @param with */ public void insertBefore(IHDLObject subject, IHDLObject... with) { if ((with == null) || (with.length == 0)) return; final IHDLObject container = subject.getContainer(); if (container != null) { final HDLFieldAccess<?, ?> feature = container.getContainingFeature(subject); if (feature != null) { if ((feature.quantifier == Quantifier.ZERO_OR_ONE) || (feature.quantifier == Quantifier.ONE)) throw new IllegalArgumentException("Can not perform insertBefore on feature: " + feature.fieldName + " not a collection"); for (final IHDLObject object : with) { if (!feature.type.isAssignableFrom(object.getClass())) throw new IllegalArgumentException("Can not insertBefore type:" + object.getClassType() + " to feature: " + feature.fieldName); } } } final Modification mod = new Modification(subject, ModificationType.INSERT_BEFORE, null, (Object[]) with); insert(subject, mod); } private void insert(IHDLObject subject, Modification mod) { List<Modification> list = replacements.get(subject); if (list == null) { list = new LinkedList<ModificationSet.Modification>(); } list.add(mod); replacements.put(subject, list); } /** * Adds objects to a feature of the subject * * @param subject * @param field * @param add */ public <T> void addTo(IHDLObject subject, HDLFieldAccess<?, ArrayList<T>> field, @SuppressWarnings("unchecked") T... add) { if ((add == null) || (add.length == 0)) return; for (final T t : add) { if (!field.type.isAssignableFrom(t.getClass())) throw new IllegalArgumentException("Can not add type: " + t.getClass() + " to feature: " + field.fieldName + ", incompatible types"); } final Modification mod = new Modification(subject, ModificationType.ADD, field.fieldName, add); insert(subject, mod); } /** * Executes all outstanding modifications or returns the original object if * nothing needs to be done * * @param orig * @return */ @SuppressWarnings("unchecked") public <T extends IHDLObject> T apply(T orig) { if (replacements.size() == 0) return orig; final T newR = getReplacement(orig); final T res = (T) newR.copyFiltered(new MSCopyFilter()); res.freeze(orig.getContainer()); return res; } /** * Checks whether any replacements are planned and returns the first object * that should replace the subject * * @param reg * @return */ @SuppressWarnings("unchecked") public <T extends IHDLObject> T getReplacement(T reg) { final List<Modification> mods = getModifications(reg); if ((mods == null) || mods.isEmpty()) return reg; final Modification mod = mods.get(0); if (mod.type != ModificationType.ADD) return (T) mod.with.get(0); return reg; } /** * Replace the subject IHDLObject with the subjects, but discard all other * planned modifications * * @param subject * @param with */ public void replacePrune(IHDLObject subject, IHDLObject... with) { prune(subject); replace(subject, with); } private void prune(IHDLObject subject) { final List<Modification> list = replacements.get(subject); if (list != null) { final Iterator<Modification> iter = list.iterator(); while (iter.hasNext()) { final ModificationSet.Modification mod = iter.next(); if (mod.subject == subject) { iter.remove(); } } } } @Override public String toString() { return replacements.toString(); } }