diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/BuildingBlock.java b/diylc/diylc-core/src/main/java/org/diylc/common/BuildingBlock.java index b7dd1653..94a707b6 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/common/BuildingBlock.java +++ b/diylc/diylc-core/src/main/java/org/diylc/common/BuildingBlock.java @@ -21,6 +21,7 @@ */ package org.diylc.common; +import java.time.LocalDateTime; import java.util.Collection; import org.diylc.core.IDIYComponent; @@ -29,6 +30,10 @@ public class BuildingBlock { private String name; private Collection> components; + // the dates are set only on factory blocks in import-defaults/blocks.xml, and a withdrawn + // factory block carries nothing but its name and deletedOn + private LocalDateTime createdOn; + private LocalDateTime deletedOn; public BuildingBlock(String name, Collection> components) { super(); @@ -52,6 +57,22 @@ public class BuildingBlock { this.components = components; } + public LocalDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(LocalDateTime createdOn) { + this.createdOn = createdOn; + } + + public LocalDateTime getDeletedOn() { + return deletedOn; + } + + public void setDeletedOn(LocalDateTime deletedOn) { + this.deletedOn = deletedOn; + } + @Override public String toString() { return name; diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/IBlockProcessor.java b/diylc/diylc-core/src/main/java/org/diylc/common/IBlockProcessor.java index 3dcc369b..a14428d1 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/common/IBlockProcessor.java +++ b/diylc/diylc-core/src/main/java/org/diylc/common/IBlockProcessor.java @@ -27,6 +27,7 @@ public interface IBlockProcessor { public static final String BLOCKS_KEY = "buildingBlocks"; public static final String DEFAULT_BLOCKS_IMPORTED_KEY = "defaultBlocksImported"; + public static final String DEFAULT_BLOCKS_IMPORT_DATE_KEY = "defaultBlocksImportDate"; /** * Prefix that marks a string as a reference to a building block rather than a component class diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/BuildingBlockManager.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/BuildingBlockManager.java index 6429f55b..78edbfb8 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/BuildingBlockManager.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/BuildingBlockManager.java @@ -18,6 +18,7 @@ package org.diylc.presenter; import java.io.*; +import java.time.LocalDateTime; import java.util.*; import org.apache.log4j.Logger; @@ -28,6 +29,7 @@ import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.security.AnyTypePermission; import org.diylc.common.BlockInstantiationMode; +import org.diylc.common.BuildingBlock; import org.diylc.common.BuildingBlockPackage; import org.diylc.common.ComponentType; import org.diylc.common.IBlockProcessor; @@ -42,7 +44,7 @@ public class BuildingBlockManager { private static final String BLOCKS_FILE_NAME = "import-defaults/blocks.xml"; - private static final Logger LOG = Logger.getLogger(VariantManager.class); + private static final Logger LOG = Logger.getLogger(BuildingBlockManager.class); private IConfigurationManager configManager; private XStream xStream; @@ -58,44 +60,80 @@ public class BuildingBlockManager { @SuppressWarnings("unchecked") public void importDefaultBlocks() { - // import default templates from variants.xml file only if we didn't do it already - if (!configManager.readBoolean(IPlugInPort.DEFAULT_BLOCKS_IMPORTED_KEY, false)) { - try { - InputStream inputStream = Presenter.class.getResourceAsStream("/" + BLOCKS_FILE_NAME); - if (inputStream == null) { - return; - } - BufferedInputStream in = new BufferedInputStream(inputStream); - Map>> defaults = - (Map>>) ProjectFileManager.xStreamSerializer - .fromXML(in); - in.close(); - - Map>> blocksMap = - (Map>>) configManager.readObject(IPlugInPort.BLOCKS_KEY, - null); - if (blocksMap == null) - blocksMap = new HashMap>>(); - - // merge default blocks with user's - for (Map.Entry>> entry : defaults.entrySet()) { - if (!blocksMap.containsKey(entry.getKey())) - blocksMap.put(entry.getKey(), entry.getValue()); - } - - // update templates and a flag marking that we imported them - configManager.writeValue(IPlugInPort.DEFAULT_BLOCKS_IMPORTED_KEY, true); - configManager.writeValue(IPlugInPort.BLOCKS_KEY, blocksMap); - int importCount = defaults.size(); - if (importCount > 0) { - LOG.info(String.format("Imported %d default building blocks", importCount)); - } - } catch (Exception e) { - LOG.error("Could not load default blocks", e); + try { + InputStream inputStream = Presenter.class.getResourceAsStream("/" + BLOCKS_FILE_NAME); + if (inputStream == null) { + return; } + BufferedInputStream in = new BufferedInputStream(inputStream); + List defaults = + (List) ProjectFileManager.xStreamSerializer.fromXML(in); + in.close(); + importDefaultBlocks(defaults); + } catch (Exception e) { + LOG.error("Could not load default blocks", e); } } - + + @SuppressWarnings("unchecked") + void importDefaultBlocks(List defaults) { + LocalDateTime importDate = (LocalDateTime) configManager + .readObject(IPlugInPort.DEFAULT_BLOCKS_IMPORT_DATE_KEY, LocalDateTime.MIN); + + Map>> blocksMap = + (Map>>) configManager.readObject(IPlugInPort.BLOCKS_KEY, + null); + if (blocksMap == null) { + blocksMap = new HashMap>>(); + } + + // as with variants, the import date is the latest date in the bundled file rather than the + // clock, and never moves backwards + LocalDateTime newImportDate = importDate; + List deletedNames = new ArrayList(); + int importCount = 0; + + for (BuildingBlock block : defaults) { + if (block.getCreatedOn() != null && block.getCreatedOn().isAfter(newImportDate)) { + newImportDate = block.getCreatedOn(); + } + if (block.getDeletedOn() != null && block.getDeletedOn().isAfter(newImportDate)) { + newImportDate = block.getDeletedOn(); + } + // a deletion stub keeps nothing but its name, so a missing createdOn only means an old + // entry when the block has not been deleted + boolean created = block.getCreatedOn() == null ? block.getDeletedOn() == null + : importDate.compareTo(block.getCreatedOn()) < 0; + boolean deleted = + block.getDeletedOn() != null && importDate.compareTo(block.getDeletedOn()) < 0; + if (deleted && !created) { + if (blocksMap.remove(block.getName()) != null) { + deletedNames.add(block.getName()); + } + } else if (created && block.getDeletedOn() == null + && !blocksMap.containsKey(block.getName())) { + // a user who had the block before the import date existed keeps their copy, edited or + // not, and one who deleted it gets it back once + blocksMap.put(block.getName(), new ArrayList>(block.getComponents())); + importCount++; + } + } + + // the flag stays so that older versions, which know nothing else, do not import again + configManager.writeValue(IPlugInPort.DEFAULT_BLOCKS_IMPORTED_KEY, true); + configManager.writeValue(IPlugInPort.DEFAULT_BLOCKS_IMPORT_DATE_KEY, newImportDate); + configManager.writeValue(IPlugInPort.BLOCKS_KEY, blocksMap); + for (String name : deletedNames) { + removeFromRecent(name); + } + if (importCount > 0) { + LOG.info(String.format("Imported %d default building blocks", importCount)); + } + if (!deletedNames.isEmpty()) { + LOG.info(String.format("Deleted %d withdrawn default building blocks", deletedNames.size())); + } + } + @SuppressWarnings("unchecked") public int importBlocks(String fileName) throws IOException { LOG.debug(String.format("importBlocks(%s)", fileName)); @@ -221,14 +259,15 @@ public class BuildingBlockManager { blocks.put(blockName, blockComponents); if (System.getProperty("org.diylc.WriteStaticBlocks", "false").equalsIgnoreCase("true")) { - Map>> defaultBlockMap = - new HashMap>>(); - defaultBlockMap.put(blockName, blockComponents); + BuildingBlock defaultBlock = new BuildingBlock(blockName, blockComponents); + defaultBlock.setCreatedOn(LocalDateTime.now().withNano(0)); + List defaultBlocks = new ArrayList(); + defaultBlocks.add(defaultBlock); try { BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(BLOCKS_FILE_NAME)); XStream xStream = new XStream(new DomDriver()); xStream.addPermission(AnyTypePermission.ANY); - xStream.toXML(defaultBlockMap, out); + xStream.toXML(defaultBlocks, out); out.close(); LOG.info("Saved default blocks"); } catch (IOException e) { @@ -248,11 +287,16 @@ public class BuildingBlockManager { blocks.remove(blockName); configManager.writeValue(IBlockProcessor.BLOCKS_KEY, blocks); } + removeFromRecent(blockName); + } - // drop the block from the recently used list too, so it doesn't linger as a dead entry + // drop the block from the recently used list too, so it doesn't linger as a dead entry + @SuppressWarnings("unchecked") + private void removeFromRecent(String blockName) { List recent = (List) configManager.readObject(IPlugInPort.RECENT_COMPONENTS_KEY, null); - if (recent != null && recent.remove(IBlockProcessor.BLOCK_PREFIX + blockName)) + if (recent != null && recent.remove(IBlockProcessor.BLOCK_PREFIX + blockName)) { configManager.writeValue(IPlugInPort.RECENT_COMPONENTS_KEY, recent); + } } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/VariantManager.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/VariantManager.java index 4e8f48ab..9a534742 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/VariantManager.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/VariantManager.java @@ -86,6 +86,11 @@ public class VariantManager { if (variantMap == null) variantMap = new HashMap>(); + // the import date records how far the bundled file reaches, not when it was read, so that + // running an older build first does not mark the variants of a newer build as already seen; + // it never moves backwards, so running an older build after a newer one changes nothing + LocalDateTime newImportDate = importDate; + // merge default variants with user's for (Map.Entry> entry : defaults.entrySet()) { List