/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.controller.yang.model.parser.builder.impl; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.opendaylight.controller.yang.common.QName; import org.opendaylight.controller.yang.model.api.AugmentationSchema; import org.opendaylight.controller.yang.model.api.DataSchemaNode; import org.opendaylight.controller.yang.model.api.Deviation; import org.opendaylight.controller.yang.model.api.ExtensionDefinition; import org.opendaylight.controller.yang.model.api.FeatureDefinition; import org.opendaylight.controller.yang.model.api.GroupingDefinition; import org.opendaylight.controller.yang.model.api.Module; import org.opendaylight.controller.yang.model.api.ModuleImport; import org.opendaylight.controller.yang.model.api.NotificationDefinition; import org.opendaylight.controller.yang.model.api.RpcDefinition; import org.opendaylight.controller.yang.model.api.TypeDefinition; import org.opendaylight.controller.yang.model.api.UsesNode; import org.opendaylight.controller.yang.model.parser.builder.api.AugmentationSchemaBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.Builder; import org.opendaylight.controller.yang.model.parser.builder.api.ChildNodeBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.DataSchemaNodeBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.GroupingBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.TypeAwareBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.TypeDefinitionAwareBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.TypeDefinitionBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.UsesNodeBuilder; /** * This builder builds Module object. If this module is dependent on external * module/modules, these dependencies must be resolved before module is built, * otherwise result may not be valid. */ public class ModuleBuilder implements Builder { private final ModuleImpl instance; private final String name; private String prefix; private Date revision; private final Set imports = new HashSet(); private Set augmentations; /** * All nodes, that can contain other nodes */ private final Map, Builder> moduleNodes = new HashMap, Builder>(); /** * Holds all child (DataSchemaNode) nodes: anyxml, choice, case, container, * list, leaf, leaf-list. */ private final Map, DataSchemaNodeBuilder> addedChilds = new HashMap, DataSchemaNodeBuilder>(); private final Map, GroupingBuilder> addedGroupings = new HashMap, GroupingBuilder>(); private final Set addedAugments = new HashSet(); private final Map, UsesNodeBuilder> addedUsesNodes = new HashMap, UsesNodeBuilder>(); private final Map, RpcDefinitionBuilder> addedRpcs = new HashMap, RpcDefinitionBuilder>(); private final Set addedNotifications = new HashSet(); private final Map, FeatureBuilder> addedFeatures = new HashMap, FeatureBuilder>(); private final Map addedDeviations = new HashMap(); private final Map, TypeDefinitionBuilder> addedTypedefs = new HashMap, TypeDefinitionBuilder>(); private final List addedExtensions = new ArrayList(); private final Map, TypeAwareBuilder> dirtyNodes = new HashMap, TypeAwareBuilder>(); private final Map, UnionTypeBuilder> unionTypes = new HashMap, UnionTypeBuilder>(); public ModuleBuilder(String name) { this.name = name; instance = new ModuleImpl(name); } /** * Build new Module object based on this builder. */ @Override public Module build() { instance.setImports(imports); // TYPEDEFS final Set> typedefs = buildModuleTypedefs(addedTypedefs); instance.setTypeDefinitions(typedefs); // CHILD NODES final Map childNodes = buildModuleChildNodes(addedChilds); instance.setChildNodes(childNodes); // GROUPINGS final Set groupings = buildModuleGroupings(addedGroupings); instance.setGroupings(groupings); // USES final Set usesNodeDefinitions = buildUsesNodes(addedUsesNodes); instance.setUses(usesNodeDefinitions); // FEATURES final Set features = buildModuleFeatures(addedFeatures); instance.setFeatures(features); // NOTIFICATIONS final Set notifications = new HashSet(); for (NotificationBuilder entry : addedNotifications) { notifications.add((NotificationDefinition) entry.build()); } instance.setNotifications(notifications); // AUGMENTATIONS instance.setAugmentations(augmentations); // RPCs final Set rpcs = buildModuleRpcs(addedRpcs); instance.setRpcs(rpcs); // DEVIATIONS final Set deviations = new HashSet(); for (Map.Entry entry : addedDeviations .entrySet()) { deviations.add(entry.getValue().build()); } instance.setDeviations(deviations); // EXTENSIONS final List extensions = new ArrayList(); for(ExtensionBuilder b : addedExtensions) { extensions.add(b.build()); } instance.setExtensionSchemaNodes(extensions); return instance; } public Builder getNode(List path) { return moduleNodes.get(path); } public Map, TypeAwareBuilder> getDirtyNodes() { return dirtyNodes; } public String getName() { return name; } public String getPrefix() { return prefix; } public Date getRevision() { return revision; } public Set getAddedAugments() { return addedAugments; } public void addDirtyNode(List path) { List dirtyNodePath = new ArrayList(path); TypeAwareBuilder nodeBuilder = (TypeAwareBuilder) moduleNodes .get(dirtyNodePath); dirtyNodes.put(dirtyNodePath, nodeBuilder); } public void setNamespace(URI namespace) { instance.setNamespace(namespace); } public void setRevision(Date revision) { this.revision = revision; instance.setRevision(revision); } public void setPrefix(String prefix) { this.prefix = prefix; instance.setPrefix(prefix); } public void setYangVersion(String yangVersion) { instance.setYangVersion(yangVersion); } public void setDescription(String description) { instance.setDescription(description); } public void setReference(String reference) { instance.setReference(reference); } public void setOrganization(String organization) { instance.setOrganization(organization); } public void setContact(String contact) { instance.setContact(contact); } public void setAugmentations(Set augmentations) { this.augmentations = augmentations; } public boolean addModuleImport(final String moduleName, final Date revision, final String prefix) { ModuleImport moduleImport = createModuleImport(moduleName, revision, prefix); return imports.add(moduleImport); } public Set getModuleImports() { return imports; } public ExtensionBuilder addExtension(QName qname) { ExtensionBuilder builder = new ExtensionBuilder(qname); return builder; } public ContainerSchemaNodeBuilder addContainerNode(QName containerName, List parentPath) { List pathToNode = new ArrayList(parentPath); ContainerSchemaNodeBuilder containerBuilder = new ContainerSchemaNodeBuilder( containerName); ChildNodeBuilder parent = (ChildNodeBuilder) moduleNodes .get(pathToNode); if (parent != null) { if(parent instanceof AugmentationSchemaBuilder) { containerBuilder.setAugmenting(true); } parent.addChildNode(containerBuilder); } pathToNode.add(containerName.getLocalName()); moduleNodes.put(pathToNode, containerBuilder); addedChilds.put(pathToNode, containerBuilder); return containerBuilder; } public ListSchemaNodeBuilder addListNode(QName listName, List parentPath) { List pathToNode = new ArrayList(parentPath); ListSchemaNodeBuilder listBuilder = new ListSchemaNodeBuilder(listName); ChildNodeBuilder parent = (ChildNodeBuilder) moduleNodes .get(pathToNode); if (parent != null) { if(parent instanceof AugmentationSchemaBuilder) { listBuilder.setAugmenting(true); } parent.addChildNode(listBuilder); } pathToNode.add(listName.getLocalName()); moduleNodes.put(pathToNode, listBuilder); addedChilds.put(pathToNode, listBuilder); return listBuilder; } public LeafSchemaNodeBuilder addLeafNode(QName leafName, List parentPath) { List pathToNode = new ArrayList(parentPath); LeafSchemaNodeBuilder leafBuilder = new LeafSchemaNodeBuilder(leafName); ChildNodeBuilder parent = (ChildNodeBuilder) moduleNodes.get(pathToNode); if (parent != null) { if(parent instanceof AugmentationSchemaBuilder) { leafBuilder.setAugmenting(true); } parent.addChildNode(leafBuilder); } pathToNode.add(leafName.getLocalName()); addedChilds.put(pathToNode, leafBuilder); moduleNodes.put(pathToNode, leafBuilder); return leafBuilder; } public LeafListSchemaNodeBuilder addLeafListNode(QName leafListName, List parentPath) { List pathToNode = new ArrayList(parentPath); LeafListSchemaNodeBuilder leafListBuilder = new LeafListSchemaNodeBuilder( leafListName); ChildNodeBuilder parent = (ChildNodeBuilder) moduleNodes.get(pathToNode); if (parent != null) { if(parent instanceof AugmentationSchemaBuilder) { leafListBuilder.setAugmenting(true); } parent.addChildNode(leafListBuilder); } pathToNode.add(leafListName.getLocalName()); addedChilds.put(pathToNode, leafListBuilder); moduleNodes.put(pathToNode, leafListBuilder); return leafListBuilder; } public GroupingBuilder addGrouping(QName qname, List parentPath) { List pathToGroup = new ArrayList(parentPath); GroupingBuilder builder = new GroupingBuilderImpl(qname); ChildNodeBuilder parentNodeBuilder = (ChildNodeBuilder) moduleNodes.get(pathToGroup); if (parentNodeBuilder != null) { parentNodeBuilder.addGrouping(builder); } pathToGroup.add(qname.getLocalName()); moduleNodes.put(pathToGroup, builder); addedGroupings.put(pathToGroup, builder); return builder; } public AugmentationSchemaBuilder addAugment(String name, List parentPath) { List pathToAugment = new ArrayList(parentPath); AugmentationSchemaBuilder builder = new AugmentationSchemaBuilderImpl(name); // augment can only be in 'module' or 'uses' statement UsesNodeBuilder parent = addedUsesNodes.get(pathToAugment); if (parent != null) { parent.addAugment(builder); } pathToAugment.add(name); moduleNodes.put(pathToAugment, builder); addedAugments.add(builder); return builder; } public UsesNodeBuilder addUsesNode(String groupingPathStr, List parentPath) { List pathToUses = new ArrayList(parentPath); UsesNodeBuilder usesBuilder = new UsesNodeBuilderImpl(groupingPathStr); ChildNodeBuilder parent = (ChildNodeBuilder) moduleNodes.get(pathToUses); if (parent != null) { if(parent instanceof AugmentationSchemaBuilder) { usesBuilder.setAugmenting(true); } parent.addUsesNode(usesBuilder); } pathToUses.add(groupingPathStr); addedUsesNodes.put(pathToUses, usesBuilder); return usesBuilder; } public RpcDefinitionBuilder addRpc(QName qname, List parentPath) { List pathToRpc = new ArrayList(parentPath); RpcDefinitionBuilder rpcBuilder = new RpcDefinitionBuilder(qname); pathToRpc.add(qname.getLocalName()); addedRpcs.put(pathToRpc, rpcBuilder); QName inputQName = new QName(qname.getNamespace(), qname.getRevision(), qname.getPrefix(), "input"); ContainerSchemaNodeBuilder inputBuilder = new ContainerSchemaNodeBuilder(inputQName); List pathToInput = new ArrayList(pathToRpc); pathToInput.add("input"); moduleNodes.put(pathToInput, inputBuilder); rpcBuilder.setInput(inputBuilder); QName outputQName = new QName(qname.getNamespace(), qname.getRevision(), qname.getPrefix(), "output"); ContainerSchemaNodeBuilder outputBuilder = new ContainerSchemaNodeBuilder(outputQName); List pathToOutput = new ArrayList(pathToRpc); pathToOutput.add("output"); moduleNodes.put(pathToOutput, outputBuilder); rpcBuilder.setOutput(outputBuilder); return rpcBuilder; } public NotificationBuilder addNotification(QName notificationName, List parentPath) { List pathToNotification = new ArrayList(parentPath); NotificationBuilder notificationBuilder = new NotificationBuilder( notificationName); pathToNotification.add(notificationName.getLocalName()); moduleNodes.put(pathToNotification, notificationBuilder); addedNotifications.add(notificationBuilder); return notificationBuilder; } public FeatureBuilder addFeature(QName featureName, List parentPath) { List pathToFeature = new ArrayList(parentPath); pathToFeature.add(featureName.getLocalName()); FeatureBuilder builder = new FeatureBuilder(featureName); addedFeatures.put(pathToFeature, builder); return builder; } public TypedefBuilder addTypedef(QName typeDefName, List parentPath) { List pathToType = new ArrayList(parentPath); TypedefBuilder builder = new TypedefBuilder(typeDefName); TypeDefinitionAwareBuilder parent = (TypeDefinitionAwareBuilder) moduleNodes.get(pathToType); if (parent != null) { parent.addTypedef(builder); } pathToType.add(typeDefName.getLocalName()); addedTypedefs.put(pathToType, builder); moduleNodes.put(pathToType, builder); return builder; } public Set getModuleTypedefs() { Set typedefs = new HashSet(); for (Map.Entry, TypeDefinitionBuilder> entry : addedTypedefs.entrySet()) { if (entry.getKey().size() == 2) { typedefs.add(entry.getValue()); } } return typedefs; } public void setType(TypeDefinition type, List parentPath) { TypeAwareBuilder parent = (TypeAwareBuilder) moduleNodes.get(parentPath); parent.setType(type); } public void addUnionType(List parentPath) { TypeAwareBuilder parent = (TypeAwareBuilder) moduleNodes.get(parentPath); UnionTypeBuilder union = new UnionTypeBuilder(); parent.setType(union.build()); List path = new ArrayList(parentPath); path.add("union"); unionTypes.put(path, union); moduleNodes.put(path, union); } public DeviationBuilder addDeviation(String targetPath) { DeviationBuilder builder = new DeviationBuilder(targetPath); addedDeviations.put(targetPath, builder); return builder; } public void addConfiguration(boolean configuration, List parentPath) { Builder builder = moduleNodes.get(parentPath); if (builder instanceof DeviationBuilder) { // skip // TODO } else { DataSchemaNodeBuilder configBuilder = (DataSchemaNodeBuilder) moduleNodes.get(parentPath); configBuilder.setConfiguration(configuration); } } public UnknownSchemaNodeBuilder addUnknownSchemaNode(QName qname, List parentPath) { UnknownSchemaNodeBuilder builder = new UnknownSchemaNodeBuilder(qname); return builder; } private class ModuleImpl implements Module { private URI namespace; private final String name; private Date revision; private String prefix; private String yangVersion; private String description; private String reference; private String organization; private String contact; private Set imports = Collections.emptySet(); private Set features = Collections.emptySet(); private Set> typeDefinitions = Collections.emptySet(); private Set notifications = Collections.emptySet(); private Set augmentations = Collections.emptySet(); private Set rpcs = Collections.emptySet(); private Set deviations = Collections.emptySet(); private Map childNodes = Collections.emptyMap(); private Set groupings = Collections.emptySet(); private Set uses = Collections.emptySet(); private List extensionSchemaNodes = Collections.emptyList(); private ModuleImpl(String name) { this.name = name; } @Override public URI getNamespace() { return namespace; } private void setNamespace(URI namespace) { this.namespace = namespace; } @Override public String getName() { return name; } @Override public Date getRevision() { return revision; } private void setRevision(Date revision) { this.revision = revision; } @Override public String getPrefix() { return prefix; } private void setPrefix(String prefix) { this.prefix = prefix; } @Override public String getYangVersion() { return yangVersion; } private void setYangVersion(String yangVersion) { this.yangVersion = yangVersion; } @Override public String getDescription() { return description; } private void setDescription(String description) { this.description = description; } @Override public String getReference() { return reference; } private void setReference(String reference) { this.reference = reference; } @Override public String getOrganization() { return organization; } private void setOrganization(String organization) { this.organization = organization; } @Override public String getContact() { return contact; } private void setContact(String contact) { this.contact = contact; } @Override public Set getImports() { return imports; } private void setImports(Set imports) { if(imports != null) { this.imports = imports; } } @Override public Set getFeatures() { return features; } private void setFeatures(Set features) { if(features != null) { this.features = features; } } @Override public Set> getTypeDefinitions() { return typeDefinitions; } private void setTypeDefinitions(Set> typeDefinitions) { if(typeDefinitions != null) { this.typeDefinitions = typeDefinitions; } } @Override public Set getNotifications() { return notifications; } private void setNotifications(Set notifications) { if(notifications != null) { this.notifications = notifications; } } @Override public Set getAugmentations() { return augmentations; } private void setAugmentations(Set augmentations) { if(augmentations != null) { this.augmentations = augmentations; } } @Override public Set getRpcs() { return rpcs; } private void setRpcs(Set rpcs) { if(rpcs != null) { this.rpcs = rpcs; } } @Override public Set getDeviations() { return deviations; } private void setDeviations(Set deviations) { if(deviations != null) { this.deviations = deviations; } } @Override public Set getChildNodes() { return new HashSet(childNodes.values()); } private void setChildNodes(Map childNodes) { if(childNodes != null) { this.childNodes = childNodes; } } @Override public Set getGroupings() { return groupings; } private void setGroupings(Set groupings) { if(groupings != null) { this.groupings = groupings; } } @Override public Set getUses() { return uses; } private void setUses(Set uses) { if(uses != null) { this.uses = uses; } } @Override public List getExtensionSchemaNodes() { return extensionSchemaNodes; } private void setExtensionSchemaNodes(List extensionSchemaNodes) { if(extensionSchemaNodes != null) { this.extensionSchemaNodes = extensionSchemaNodes; } } @Override public DataSchemaNode getDataChildByName(QName name) { return childNodes.get(name); } @Override public DataSchemaNode getDataChildByName(String name) { DataSchemaNode result = null; for (Map.Entry entry : childNodes.entrySet()) { if (entry.getKey().getLocalName().equals(name)) { result = entry.getValue(); break; } } return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((revision == null) ? 0 : revision.hashCode()); result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); result = prime * result + ((yangVersion == null) ? 0 : yangVersion.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ModuleImpl other = (ModuleImpl) obj; if (namespace == null) { if (other.namespace != null) { return false; } } else if (!namespace.equals(other.namespace)) { return false; } if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } if (revision == null) { if (other.revision != null) { return false; } } else if (!revision.equals(other.revision)) { return false; } if (prefix == null) { if (other.prefix != null) { return false; } } else if (!prefix.equals(other.prefix)) { return false; } if (yangVersion == null) { if (other.yangVersion != null) { return false; } } else if (!yangVersion.equals(other.yangVersion)) { return false; } return true; } @Override public String toString() { StringBuilder sb = new StringBuilder( ModuleImpl.class.getSimpleName()); sb.append("[\n"); sb.append("name=" + name + ",\n"); sb.append("namespace=" + namespace + ",\n"); sb.append("revision=" + revision + ",\n"); sb.append("prefix=" + prefix + ",\n"); sb.append("yangVersion=" + yangVersion + ",\n"); sb.append("description=" + description + ",\n"); sb.append("reference=" + reference + ",\n"); sb.append("organization=" + organization + ",\n"); sb.append("contact=" + contact + ",\n"); sb.append("childNodes=" + childNodes.values() + ",\n"); sb.append("groupings=" + groupings + ",\n"); sb.append("imports=" + imports + ",\n"); sb.append("features=" + features + ",\n"); sb.append("typeDefinitions=" + typeDefinitions + ",\n"); sb.append("notifications=" + notifications + ",\n"); sb.append("augmentations=" + augmentations + ",\n"); sb.append("rpcs=" + rpcs + ",\n"); sb.append("deviations=" + deviations + "\n"); sb.append("uses=" + uses + "\n"); sb.append("]"); return sb.toString(); } } private ModuleImport createModuleImport(final String moduleName, final Date revision, final String prefix) { ModuleImport moduleImport = new ModuleImport() { @Override public String getModuleName() { return moduleName; } @Override public Date getRevision() { return revision; } @Override public String getPrefix() { return prefix; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((moduleName == null) ? 0 : moduleName.hashCode()); result = prime * result + ((revision == null) ? 0 : revision.hashCode()); result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ModuleImport other = (ModuleImport) obj; if (getModuleName() == null) { if (other.getModuleName() != null) { return false; } } else if (!getModuleName().equals(other.getModuleName())) { return false; } if (getRevision() == null) { if (other.getRevision() != null) { return false; } } else if (!getRevision().equals(other.getRevision())) { return false; } if (getPrefix() == null) { if (other.getPrefix() != null) { return false; } } else if (!getPrefix().equals(other.getPrefix())) { return false; } return true; } @Override public String toString() { return "ModuleImport[moduleName=" + moduleName + ", revision=" + revision + ", prefix=" + prefix + "]"; } }; return moduleImport; } /** * Traverse through given addedChilds and add only direct module childs. * Direct module child path size is 2 (1. module name, 2. child name). * * @param addedChilds * @return map of children, where key is child QName and value is child * itself */ private Map buildModuleChildNodes( Map, DataSchemaNodeBuilder> addedChilds) { final Map childNodes = new HashMap(); for (Map.Entry, DataSchemaNodeBuilder> entry : addedChilds .entrySet()) { if (entry.getKey().size() == 2) { DataSchemaNode node = entry.getValue().build(); QName qname = entry.getValue().getQName(); childNodes.put(qname, node); } } return childNodes; } /** * Traverse through given addedGroupings and add only direct module * groupings. Direct module grouping path size is 2 (1. module name, 2. * grouping name). * * @param addedGroupings * @return set of built GroupingDefinition objects */ private Set buildModuleGroupings( Map, GroupingBuilder> addedGroupings) { final Set groupings = new HashSet(); for (Map.Entry, GroupingBuilder> entry : addedGroupings .entrySet()) { if (entry.getKey().size() == 2) { groupings.add(entry.getValue().build()); } } return groupings; } /** * Traverse through given addedRpcs and build RpcDefinition objects. * * @param addedRpcs * @return set of built RpcDefinition objects */ private Set buildModuleRpcs( Map, RpcDefinitionBuilder> addedRpcs) { final Set rpcs = new HashSet(); RpcDefinitionBuilder builder; for (Map.Entry, RpcDefinitionBuilder> entry : addedRpcs .entrySet()) { builder = entry.getValue(); RpcDefinition rpc = builder.build(); rpcs.add(rpc); } return rpcs; } /** * Traverse through given addedTypedefs and add only direct module typedef * statements. Direct module typedef path size is 2 (1. module name, 2. * typedef name). * * @param addedTypedefs * @return set of built module typedef statements */ private Set> buildModuleTypedefs( Map, TypeDefinitionBuilder> addedTypedefs) { Set> typedefs = new HashSet>(); for (Map.Entry, TypeDefinitionBuilder> entry : addedTypedefs .entrySet()) { if (entry.getKey().size() == 2) { TypeDefinition> node = entry .getValue().build(); typedefs.add(node); } } return typedefs; } /** * Traverse through given addedUsesNodes and add only direct module uses * nodes. Direct module uses node path size is 2 (1. module name, 2. uses * name). * * @param addedUsesNodes * @return set of built module uses nodes */ private Set buildUsesNodes( Map, UsesNodeBuilder> addedUsesNodes) { final Set usesNodeDefinitions = new HashSet(); for (Map.Entry, UsesNodeBuilder> entry : addedUsesNodes .entrySet()) { if (entry.getKey().size() == 2) { usesNodeDefinitions.add(entry.getValue().build()); } } return usesNodeDefinitions; } /** * Traverse through given addedFeatures and add only direct module features. * Direct module feature path size is 2 (1. module name, 2. feature name). * * @param addedFeatures * @return set of built module features */ private Set buildModuleFeatures( Map, FeatureBuilder> addedFeatures) { Set features = new HashSet(); for (Map.Entry, FeatureBuilder> entry : addedFeatures .entrySet()) { if (entry.getKey().size() == 2) { features.add(entry.getValue().build()); } } return features; } }