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}