001package org.biopax.paxtools.controller; 002 003import org.apache.commons.logging.Log; 004import org.apache.commons.logging.LogFactory; 005import org.biopax.paxtools.model.BioPAXElement; 006import org.biopax.paxtools.model.BioPAXLevel; 007import org.biopax.paxtools.model.level2.control; 008import org.biopax.paxtools.model.level2.conversion; 009import org.biopax.paxtools.model.level3.Control; 010import org.biopax.paxtools.model.level3.Conversion; 011import org.biopax.paxtools.util.AbstractFilterSet; 012import org.biopax.paxtools.util.CompositeIterator; 013import org.biopax.paxtools.util.IllegalBioPAXArgumentException; 014 015import java.util.*; 016 017 018/** 019 * This is the base adapter for all editor maps. A {@link PropertyEditor} is an object that can 020 * manipulate a certain property. Editor maps are responsible for initializing these editors and 021 * providing them. The default level is the latest official release of BioPAX. 022 */ 023public class EditorMapImpl implements EditorMap { 024 025 /** 026 * A map from property names to actual property editors. Since EditorMap keeps a separate editor for each target 027 * class( This is necessary to be able to validate class specific restrictions without instanceof checks), 028 * values are set of editors. 029 */ 030 protected final Map<String, Set<PropertyEditor>> propertyToEditorMap = new HashMap<String, Set<PropertyEditor>>(); 031 032 /** 033 * A map from classes to their registered editors. 034 */ 035 protected final Map<Class<? extends BioPAXElement>, Map<String, PropertyEditor>> classToEditorMap = 036 037 new HashMap<Class<? extends BioPAXElement>, Map<String, PropertyEditor>>(); 038 039 /** 040 * A map from classes to their registered inverse editors 041 */ 042 protected final Map<Class<? extends BioPAXElement>, Set<ObjectPropertyEditor>> classToInverseEditorMap = 043 new HashMap<Class<? extends BioPAXElement>, Set<ObjectPropertyEditor>>(); 044 045 /** 046 * Another map to keep editors as a set rather than a map. This is a small efficiency tweak as editors are create 047 * once objects, query multiple times objects. 048 */ 049 protected final Map<Class<? extends BioPAXElement>, Set<PropertyEditor>> classToEditorSet = 050 new HashMap<Class<? extends BioPAXElement>, Set<PropertyEditor>>(); 051 052 053 private static final Log log = LogFactory.getLog(EditorMapImpl.class); 054 055 protected final BioPAXLevel level; 056 057 public EditorMapImpl(BioPAXLevel level) 058 { 059 this.level = level; 060 } 061 062 063 public Set<PropertyEditor> getEditorsOf(BioPAXElement bpe) { 064 return getEditorsOf(bpe.getModelInterface()); 065 } 066 067 public Set<PropertyEditor> getEditorsOf(Class<? extends BioPAXElement> domain) { 068 return this.classToEditorSet.get(domain); 069 } 070 071 public Set<ObjectPropertyEditor> getInverseEditorsOf(BioPAXElement bpe) { 072 return this.getInverseEditorsOf(bpe.getModelInterface()); 073 } 074 075 public Set<ObjectPropertyEditor> getInverseEditorsOf(Class<? extends BioPAXElement> domain) { 076 return this.classToInverseEditorMap.get(domain); 077 } 078 079 080 public <D extends BioPAXElement> PropertyEditor<? super D, ?> getEditorForProperty(String property, 081 Class<D> javaClass) 082 083 { 084 Map<String, PropertyEditor> classEditors = this.classToEditorMap.get(javaClass); 085 PropertyEditor<? super D, ?> result = null; 086 if (classEditors != null) { 087 result = classEditors.get(property); 088 089 if (result == null) { 090 if (log.isDebugEnabled()) log.debug("Could not locate controller for " + property + " | " + 091 javaClass); 092 } 093 } 094 return result; 095 096 } 097 098 public <D extends BioPAXElement> Set<PropertyEditor<? extends D, ?>> getSubclassEditorsForProperty(String 099 property, 100 Class<D> domain) { 101 return new SubDomainFilterSet<D>(this.getEditorsForProperty(property), domain); 102 103 } 104 105 public Set<PropertyEditor> getEditorsForProperty(String property) { 106 return this.propertyToEditorMap.get(property); 107 } 108 109 public <E extends BioPAXElement> Set<? extends Class<E>> getKnownSubClassesOf(Class<E> javaClass) { 110 return new SubClassFilterSet(classToEditorMap.keySet(), javaClass); 111 } 112 113 114 protected boolean isInBioPAXNameSpace(String nameSpace) { 115 return nameSpace != null && nameSpace.startsWith(BioPAXLevel.BP_PREFIX); 116 } 117 118 protected PropertyEditor createAndRegisterBeanEditor(String pName, Class domain, 119 Map<Class<? extends BioPAXElement>, Set<Class<? extends BioPAXElement>>> rRestrictions) { 120 PropertyEditor editor = AbstractPropertyEditor.createPropertyEditor(domain, pName); 121 if (editor instanceof ObjectPropertyEditor && rRestrictions != null) { 122 ((ObjectPropertyEditor) editor).setRangeRestriction(rRestrictions); 123 } 124 125 if (editor != null) { 126 Set<PropertyEditor> beanEditorsForProperty = this.propertyToEditorMap.get(pName); 127 if (beanEditorsForProperty == null) { 128 beanEditorsForProperty = new HashSet<PropertyEditor>(); 129 propertyToEditorMap.put(pName, beanEditorsForProperty); 130 } 131 beanEditorsForProperty.add(editor); 132 133 registerEditorsWithSubClasses(editor, domain); 134 } else { 135 if (log.isWarnEnabled()) log.warn("property = " + pName + "\ndomain = " + domain); 136 } 137 return editor; 138 } 139 140 /** 141 * This method registers an editor with sub classes - i.e. inserts the editor to the proper value in editor maps. 142 * @param editor to be registered 143 * @param domain a subclass of the editor's original domain. 144 */ 145 protected void registerEditorsWithSubClasses(PropertyEditor editor, Class<? extends BioPAXElement> domain) { 146 147 for (Class<? extends BioPAXElement> c : classToEditorMap.keySet()) 148 { 149 if (domain.isAssignableFrom(c)) { 150 //workaround for participants - can be replaced w/ a general 151 // annotation based system. For the time being, I am just handling it 152 //as a special case 153 if ((editor.getProperty().equals("PARTICIPANTS") && 154 (conversion.class.isAssignableFrom(c) || control.class.isAssignableFrom(c))) || 155 (editor.getProperty().equals("participant") && 156 (Conversion.class.isAssignableFrom(c) || Control.class.isAssignableFrom(c)))) { 157 if (log.isDebugEnabled()) { 158 log.debug("skipping restricted participant property"); 159 } 160 } else { 161 classToEditorMap.get(c).put(editor.getProperty(), editor); 162 } 163 } 164 165 } 166 167 if (editor instanceof ObjectPropertyEditor) { 168 registerInverseEditors((ObjectPropertyEditor) editor); 169 } 170 } 171 172 private void registerInverseEditors(ObjectPropertyEditor editor) { 173 if (editor.hasInverseLink()) { 174 for (Class<? extends BioPAXElement> c : classToInverseEditorMap.keySet()) { 175 if (checkInverseRangeIsAssignable(editor, c)) { 176 classToInverseEditorMap.get(c).add(editor); 177 } 178 } 179 } 180 } 181 182 private boolean checkInverseRangeIsAssignable(ObjectPropertyEditor editor, Class<? extends BioPAXElement> c) { 183 if (editor.getRange().isAssignableFrom(c)) { 184 Set<Class<? extends BioPAXElement>> restrictedRanges = editor.getRestrictedRangesFor(editor.getDomain()); 185 if (restrictedRanges.isEmpty()) return true; 186 else { 187 for (Class<? extends BioPAXElement> restrictedRange : restrictedRanges) { 188 if (restrictedRange.isAssignableFrom(c)) { 189 return true; 190 } 191 192 } 193 return false; 194 } 195 } 196 return false; 197 } 198 199 /** 200 * This method inserts the class into internal hashmaps and initializes the value collections. 201 * @param localName of the BioPAX class. 202 */ 203 protected void registerModelClass(String localName) { 204 try { 205 Class<? extends BioPAXElement> domain = this.getLevel().getInterfaceForName(localName); 206 207 HashMap<String, PropertyEditor> peMap = new HashMap<String, PropertyEditor>(); 208 classToEditorMap.put(domain, peMap); 209 classToInverseEditorMap.put(domain, new HashSet<ObjectPropertyEditor>()); 210 classToEditorSet.put(domain, new ValueSet(peMap.values())); 211 } catch (IllegalBioPAXArgumentException e) { 212 if (log.isDebugEnabled()) { 213 log.debug("Skipping (" + e.getMessage() + ")"); 214 } 215 } 216 } 217 218 219 private class SubClassFilterSet<E> extends AbstractFilterSet<Class<? extends BioPAXElement>, Class<E>> { 220 private Class superClass; 221 222 public SubClassFilterSet(Set<Class<? extends BioPAXElement>> baseSet, Class<E> superClass) { 223 super(baseSet); 224 this.superClass = superClass; 225 } 226 227 228 @Override 229 public boolean filter(Class<? extends BioPAXElement> subClass) { 230 return superClass.isAssignableFrom(subClass); 231 } 232 } 233 234 private class SubDomainFilterSet<D extends BioPAXElement> 235 extends AbstractFilterSet<PropertyEditor, PropertyEditor<? extends D, ?>> { 236 private Class<D> domain; 237 238 public SubDomainFilterSet(Set<PropertyEditor> baseSet, Class<D> domain) { 239 super(baseSet); 240 this.domain = domain; 241 } 242 243 @Override 244 public boolean filter(PropertyEditor editor) { 245 return domain.isAssignableFrom(editor.getDomain()); 246 } 247 } 248 249 250 private class ValueSet extends AbstractSet<PropertyEditor> { 251 Collection values; 252 253 public ValueSet(Collection<PropertyEditor> values) { 254 this.values = values; 255 } 256 257 @Override 258 public Iterator<PropertyEditor> iterator() { 259 return values.iterator(); 260 } 261 262 @Override 263 public int size() { 264 return values.size(); 265 } 266 } 267 268 public Iterator<PropertyEditor> iterator() 269 { 270 return new CompositeIterator<PropertyEditor>(propertyToEditorMap.values()); 271 } 272 273 public BioPAXLevel getLevel() 274 { 275 return level; 276 } 277 278}