-// $Id$
/**
* Copyright (C) 2007 EDIT
* European Distributed Institute of Taxonomy
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.ui.IMemento;
-
-import eu.etaxonomy.cdm.model.common.DefinedTermBase;
-import eu.etaxonomy.cdm.model.common.TermBase;
-import eu.etaxonomy.cdm.model.common.TermType;
-import eu.etaxonomy.cdm.model.common.TermVocabulary;
-import eu.etaxonomy.taxeditor.model.ContextListenerAdapter;
+import eu.etaxonomy.cdm.model.term.DefinedTermBase;
+import eu.etaxonomy.cdm.model.term.TermBase;
+import eu.etaxonomy.cdm.model.term.TermType;
+import eu.etaxonomy.cdm.model.term.TermVocabulary;
+import eu.etaxonomy.cdm.persistence.dto.TermDto;
+import eu.etaxonomy.cdm.persistence.dto.TermVocabularyDto;
+import eu.etaxonomy.taxeditor.model.DefaultTermComparator;
import eu.etaxonomy.taxeditor.model.TaxonRelationshipTypeInverseContainer;
+import eu.etaxonomy.taxeditor.preference.PreferencesUtil;
/**
* @author n.hoffmann
* @created Dec 7, 2010
- * @version 1.0
*/
-public class TermManager extends ContextListenerAdapter{
-
- private static final String TERMS = "terms";
- private static final String TERM = "term";
- private static final String HIDE = "hide";
+public class TermManager {
- private static final String CLASS = "class";
- private static final String UUID_STRING = "uuid";
+ private Map<Object, List> cachedTermMap = new HashMap<>();
- private final Map<String, Collection<UUID>> hiddenTermsMap = new HashMap<String, Collection<UUID>>();
+ //new cache for features!!
+ //dinstinguish preferences for both
+ //load at start
public TermManager(){
- CdmStore.getContextManager().addContextListener(this);
- }
-
- /**
- *
- * @param clazz
- * @return
- */
- public <T extends DefinedTermBase> List<T> getPreferredTerms(TermType termType){
- return getFilteredTerms(TermStore.<T>getTerms(termType, null));
- }
-
- /**
- *
- * @param clazz
- * @return
- */
- public <T extends DefinedTermBase> List<T> getPreferredTerms(TermVocabulary<T> termVocabulary){
- return getFilteredTerms(new ArrayList<T>(TermStore.getTerms(termVocabulary, null)));
- }
-
- /**
- *
- * @param clazz
- * @return
- */
- public <T extends DefinedTermBase> List<T> getPreferredTerms(Class<T> clazz){
- return getFilteredTerms(TermStore.getTerms(clazz));
}
- @Override
- public void contextStart(IMemento memento, IProgressMonitor monitor) {
- // read preferred terms from memento into an in memory representation
- if(memento == null){
- return;
- }
-
- IMemento terms = memento.getChild(TERMS);
-
- if(terms == null){
- return;
- }
-
- for(IMemento term : terms.getChildren(TERM)){
- String typeName = term.getString(CLASS);
- Collection<UUID> hiddenTermUuids = new ArrayList<UUID>();
- for(IMemento hide : term.getChildren(HIDE)){
- String uuidString = hide.getString(UUID_STRING);
- UUID uuid = UUID.fromString(uuidString);
- hiddenTermUuids.add(uuid);
- }
- hiddenTermsMap.put(typeName, hiddenTermUuids);
- }
+ public void reset(){
+ cachedTermMap = new HashMap<>();
}
- @Override
- public void contextStop(IMemento memento, IProgressMonitor monitor) {
- saveTerms(memento, monitor);
+ public <T extends DefinedTermBase> List<T> getPreferredTerms(TermType termType){
+ List<?> terms = cachedTermMap.get(termType.getUuid());
+ List<T> termBaseList = new ArrayList<>();
+ if(terms==null || terms.isEmpty()){
+ termBaseList = getFilteredTerms(TermStore.<T>getTerms(termType, null));
+ if (termBaseList != null){
+ cachedTermMap.put(termType.getUuid(), termBaseList);
+ }
+ }else{
+ Iterator<?> iterator = terms.iterator();
+ List<UUID> uuids = new ArrayList<>();
+ while (iterator.hasNext()){
+ Object term = iterator.next();
+ if (term instanceof TermDto){
+ TermDto dto = (TermDto)term;
+ uuids.add(dto.getUuid());
+ }else {
+ termBaseList.add((T)term);
+ }
+
+ }
+ if (!uuids.isEmpty()){
+ termBaseList.addAll(getTerms(uuids, termType));
+ }
+ }
+ return termBaseList;
}
- @Override
- public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
- saveTerms(memento, monitor);
+ public <T extends DefinedTermBase> List<T> getPreferredTerms(TermVocabulary<T> termVocabulary){
+ @SuppressWarnings("unchecked")
+ List<T> terms = cachedTermMap.get(termVocabulary.getUuid());
+ if(terms==null){
+ terms = getFilteredTerms(new ArrayList<>(TermStore.getTerms(termVocabulary,null)));
+ cachedTermMap.put(termVocabulary.getUuid(), terms);
+ }
+ return terms;
}
- private void saveTerms(IMemento memento, IProgressMonitor monitor){
- if(memento == null) {
- return;
- }
+ public List<TermDto> getPreferredTerms(TermVocabularyDto termVocabularyDto){
+ @SuppressWarnings("unchecked")
+ List<TermDto> terms = cachedTermMap.get(termVocabularyDto.getUuid());
+ if(terms==null || terms.isEmpty()){
+ terms = getFilteredTermDtos(new ArrayList<>(termVocabularyDto.getTerms()));
+ cachedTermMap.put(termVocabularyDto.getUuid(), terms);
+ }
+ return terms;
+ }
+
+ public <T extends DefinedTermBase> List<T> getPreferredTerms(TermVocabulary<T> termVocabulary,
+ Comparator comp){
+ if (comp == null){
+ comp = new DefaultTermComparator<T>();
+ }
+ @SuppressWarnings("unchecked")
+ List<T> terms = cachedTermMap.get(termVocabulary.getUuid());
+ List<T> termBaseList = new ArrayList<>();
+ if(terms==null){
+ termBaseList = getFilteredTerms(new ArrayList<T>(TermStore.getTerms(termVocabulary, comp)));
+ cachedTermMap.put(termVocabulary.getUuid(), termBaseList);
+ }else{
+ Iterator<?> iterator = terms.iterator();
+ List<UUID> uuids = new ArrayList<>();
+ while (iterator.hasNext()){
+ Object term = iterator.next();
+ if (term instanceof TermDto){
+ TermDto dto = (TermDto)term;
+ uuids.add(dto.getUuid());
+ }else {
+ termBaseList.add((T)term);
+ }
+ }
+
+ if (!uuids.isEmpty()){
+ termBaseList.addAll(this.getTerms(uuids, DefinedTermBase.class));
+ }
+
+ }
+ termBaseList.sort(comp);
+ terms = termBaseList;
+ return terms;
+ }
- IMemento terms = memento.createChild(TERMS);
- for (String preferredTerm : hiddenTermsMap.keySet()){
- IMemento term = terms.createChild(TERM);
- term.putString(CLASS, preferredTerm);
- for(UUID uuid : hiddenTermsMap.get(preferredTerm)){
- IMemento hide = term.createChild(HIDE);
- hide.putString(UUID_STRING, uuid.toString());
- }
- }
+ public <T extends DefinedTermBase> List<T> getPreferredTerms(Class<T> clazz){
+ @SuppressWarnings("unchecked")
+ List<T> terms = cachedTermMap.get(clazz);
+ if(terms==null){
+ terms = getFilteredTerms(TermStore.getTerms(clazz));
+ cachedTermMap.put(clazz, terms);
+ }
+ return terms;
}
/**
* @return a {@link java.util.List} object.
*/
public <T extends DefinedTermBase> List<T> getFilteredTerms(List<T> initialTerms){
-
- List<T> filteredTerms = new ArrayList<T>();
-
- String typeName = getTermClass(initialTerms);
- Collection<UUID> hiddenTermUuids = hiddenTermsMap.get(typeName);
-
-
- if(hiddenTermUuids == null){
- return initialTerms;
+ List<T> filteredTerms = new ArrayList<>();
+ if (initialTerms == null || initialTerms.isEmpty()){
+ return filteredTerms;
}
for (T term : initialTerms){
if (term instanceof TaxonRelationshipTypeInverseContainer){
- if(! hiddenTermUuids.contains(((TaxonRelationshipTypeInverseContainer) term).getType().getUuid())){
+ if(!PreferencesUtil.getBooleanValue(getPrefName(term), true)){
T type = (T)((TaxonRelationshipTypeInverseContainer) term).getType();
filteredTerms.add(type);
-
}
}
- if(! hiddenTermUuids.contains(term.getUuid())){
+
+ if(PreferencesUtil.getBooleanValue(getPrefName(term), true)){
filteredTerms.add(term);
}
}
+ if (filteredTerms.isEmpty()){
+ //check for dtos
+ List<TermDto> preferredTerms = null;
+ if (initialTerms.get(0).getVocabulary() != null){
+ preferredTerms = cachedTermMap.get(initialTerms.get(0).getVocabulary().getUuid());
+ }
+ if (preferredTerms == null){
+ preferredTerms = cachedTermMap.get(initialTerms.get(0).getTermType());
+ }
+ if (preferredTerms == null){
+ return initialTerms;
+ }
+ for (T term : initialTerms){
+ if (preferredTerms.contains(TermDto.fromTerm(term))){
+ filteredTerms.add(term);
+ }
+ }
+ if (filteredTerms.isEmpty()){
+ return initialTerms;
+ }
+ }
return filteredTerms;
}
- private <T extends DefinedTermBase> String getTermClass(Collection<T> initialTerms){
+ /**
+ * Generic method to get term preferences for a term vocabulary
+ *
+ * @param initialTerms a {@link java.util.List} object.
+ * @return a {@link java.util.List} object.
+ */
+ public List<TermDto> getFilteredTermDtos(List<TermDto> initialTerms){
+
+ List<TermDto> filteredTerms = new ArrayList<>();
+
+ for (TermDto term : initialTerms){
+ //TODO:inverse terms!!
+ if(PreferencesUtil.getBooleanValue(getPrefNameByDto(term))){
+ filteredTerms.add(term);
+ }
+ }
+ if (filteredTerms.isEmpty()){
+ return initialTerms;
+ }
- String result = null;
- if(!initialTerms.isEmpty()){
+ return filteredTerms;
+ }
+
+ private <T extends TermBase> String getPrefName(T term) {
+ return term.getTermType()!=null?term.getTermType().toString()+term.getUuid().toString():""+term.getUuid().toString();
+ }
+
+ private String getPrefNameByDto(TermDto term) {
+ return term.getTermType()!=null?term.getTermType().toString()+term.getUuid().toString():""+term.getUuid().toString();
+ }
- //TODO: there should be a more generic solution!!
+ private <T extends DefinedTermBase> TermVocabulary<T> getTermVocabulary(Collection<T> initialTerms){
+
+ if(!initialTerms.isEmpty()){
T entity = initialTerms.iterator().next();
- if (entity instanceof TaxonRelationshipTypeInverseContainer){
- result = ((TaxonRelationshipTypeInverseContainer) entity).getType().getClass().getName();
- } else {
- result = entity.getClass().getName();
- }
- return result;
+ return entity.getVocabulary();
}
return null;
}
-// /**
-// * <p>getPreferredSpecimenTypeDesignationStatus</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<SpecimenTypeDesignationStatus> getPreferredSpecimenTypeDesignationStatus() { return getFilteredTerms(TermStore.getSpecimenTypeDesignationStatus());}
-//
-// /**
-// * <p>getPreferredNameTypeDesignationStatus</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<NameTypeDesignationStatus> getPreferredNameTypeDesignationStatus() { return getFilteredTerms(TermStore.getNameTypeDesignationStatus()); }
-//
-// /**
-// * <p>getPreferredTaxonRelationshipTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<TaxonRelationshipType> getPreferredTaxonRelationshipTypes() { return getFilteredTerms(TermStore.getTaxonRelationshipTypes());}
-//
-// private static List<TaxonRelationshipType> excludeTaxonRelationshipTypes = Arrays.asList(new TaxonRelationshipType[]{
-// TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN(),
-// TaxonRelationshipType.MISAPPLIED_NAME_FOR(),
-// TaxonRelationshipType.ALL_RELATIONSHIPS()
-// });
-//
-// /**
-// * Please note that the {@link TaxonRelationshipType}s TAXONOMICALLY_INCLUDED_IN, MISAPPLIED_NAME_FOR and ALL_RELATIONSHIPS
-// * are filtered out as they are most likely not needed. If you do need them please refactor this method to your needs.
-// *
-// * @return a list of {@link TaxonRelationshipTypeInverseContainer} elements
-// */
-// public List<TaxonRelationshipTypeInverseContainer> getPreferredRelationshipTypesWithInverses(){
-// List<TaxonRelationshipTypeInverseContainer> relationshipTypeInverseContainers = new ArrayList<TaxonRelationshipTypeInverseContainer>();
-//
-// List<TaxonRelationshipType> relationshipTypes = getPreferredTaxonRelationshipTypes();
-//
-// relationshipTypes.removeAll(excludeTaxonRelationshipTypes);
-//
-// for (TaxonRelationshipType relationshipType : relationshipTypes){
-// if(!relationshipType.isSymmetric()){
-// TaxonRelationshipTypeInverseContainer inverseContainer = new TaxonRelationshipTypeInverseContainer(relationshipType, true);
-// relationshipTypeInverseContainers.add(inverseContainer);
-// }
-// TaxonRelationshipTypeInverseContainer container = new TaxonRelationshipTypeInverseContainer(relationshipType, false);
-// relationshipTypeInverseContainers.add(container);
-// }
-//
-// return relationshipTypeInverseContainers;
-// }
-
-
-
-
-//
-// /**
-// * <p>getPreferredFeatures</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<Feature> getPreferredFeatures() { return getFilteredTerms(TermStore.getFeatures());}
-//
-// /**
-// * <p>getPreferredRanks</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<Rank> getPreferredRanks() { return getFilteredTerms(TermStore.getRanks());}
-//
-// /**
-// * <p>getPreferredPresenceAbsenceTerms</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<PresenceAbsenceTermBase> getPreferredPresenceAbsenceTerms(){ return getFilteredTerms(TermStore.getPresenceAbsenceTerms());}
-//
-// /**
-// * <p>getPreferredNomenclaturalStatusTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<NomenclaturalStatusType> getPreferredNomenclaturalStatusTypes(){ return getFilteredTerms(TermStore.getNomenclaturalStatusTypes());}
-//
-// /**
-// * <p>getPreferredNameRelationshipTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<NameRelationshipType> getPreferredNameRelationshipTypes(){ return getFilteredTerms(TermStore.getNameRelationshipTypes());}
-//
-// /**
-// * <p>getPreferredLanguages</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<Language> getPreferredLanguages() { return getFilteredTerms(TermStore.getLanguages()); }
-//
-// /**
-// * <p>getPreferredMarkerTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<MarkerType> getPreferredMarkerTypes() { return getFilteredTerms(TermStore.getNonTechnicalMarkerTypes()); }
-//
-// /**
-// * <p>getPreferredExtensionTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<ExtensionType> getPreferredExtensionTypes() { return getFilteredTerms(TermStore.getExtensionTypes()); }
-//
-// /**
-// * <p>getPreferredRightsTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<RightsType> getPreferredRightsTypes() { return getFilteredTerms(TermStore.getRightsTypes());}
-//
-// /**
-// * <p>getPreferredNamedAreaTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<NamedAreaType> getPreferredNamedAreaTypes() { return getFilteredTerms(TermStore.getNamedAreaTypes()); }
-//
-// /**
-// * <p>getPreferredNamedAreaTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<NamedAreaLevel> getPreferredNamedAreaLevels() { return getFilteredTerms(TermStore.getNamedAreaLevels()); }
-//
-// /**
-// * <p>getPreferredAnnotationTypes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<AnnotationType> getPreferredAnnotationTypes() { return getFilteredTerms(TermStore.getAnnotationTypes()); }
-//
-// /**
-// * <p>getPreferredStages</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<Stage> getPreferredStages() { return getFilteredTerms(TermStore.getStages()); }
-//
-// /**
-// * <p>getPreferredPreservationMethods</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<PreservationMethod> getPreferredPreservationMethods() { return getFilteredTerms(TermStore.getPreservationMethods()); }
-//
-// /**
-// * <p>getPreferredMeasurementUnits</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<MeasurementUnit> getPreferredMeasurementUnits() { return getFilteredTerms(TermStore.getMeasurementUnits()); }
-//
-// /**
-// * <p>getPreferredStates</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<State> getPreferredStates() { return getFilteredTerms(TermStore.getStates()); }
-//
-// /**
-// * <p>getPreferredModifiers</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<Modifier> getPreferredModifiers() { return getFilteredTerms(TermStore.getModifiers()); }
-//
-// /**
-// * <p>getPreferredStatisticalMeasures</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<StatisticalMeasure> getPreferredStatisticalMeasures() { return getFilteredTerms(TermStore.getStatisticalMeasures()); }
-//
-// /**
-// * <p>getPreferredScopes</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<Scope> getPreferredScopes() { return getFilteredTerms(TermStore.getScopes()); }
-//
-// /**
-// * <p>getPreferredDeterminationModifiers</p>
-// *
-// * @return a {@link java.util.List} object.
-// */
-// public List<DeterminationModifier> getPreferredDeterminationModifiers() { return getFilteredTerms(TermStore.getDeterminationModifiers()); }
-//
/**
* Generic method to set term preferences
*
* @param <T> a T object.
*/
public <T extends DefinedTermBase> void setPreferredTerms(Collection<T> preferredTerms, Collection<T> initialTerms){
- String typeName = getTermClass(initialTerms);
-
- Collection<UUID> undesiredTermUuids = new ArrayList<UUID>();
-
+ TermVocabulary<T> voc = getTermVocabulary(initialTerms);
+ Collection<UUID> undesiredTermUuids = new ArrayList<>();
for(TermBase term : initialTerms){
if(! preferredTerms.contains(term)){
undesiredTermUuids.add(term.getUuid());
+ PreferencesUtil.setBooleanValue(getPrefName(term), false);
+ }else{
+ PreferencesUtil.setBooleanValue(getPrefName(term), true);
}
}
+ clearTermMapForTermVoc(voc.getUuid());
+ }
+
+ /**
+ * Generic method to set term preferences
+ *
+ * @param preferredTerms a {@link java.util.List} object.
+ * @param initialTerms a {@link java.util.List} object.
+ * @param <T> a T object.
+ */
+ public void setPreferredTermsByType(Collection<TermDto> preferredTerms, TermType type){
+ List<?> oldValues = getPreferredTerms(type);
+ if (oldValues != null){
+ for (Object term: oldValues){
+ if (term instanceof TermDto){
+ PreferencesUtil.setBooleanValue(getPrefNameByDto((TermDto)term), false);
+ }else{
+ PreferencesUtil.setBooleanValue(getPrefName((DefinedTermBase)term), false);
+ }
+ }
+ }
+// for(TermDto term : preferredTerms){
+// PreferencesUtil.setBooleanValue(getPrefNameByDto(term), true);
+//
+// }
+
+ clearTermMapForTermType(type);
+ List<TermDto> list = new ArrayList<>(preferredTerms);
+ cachedTermMap.put(type, list);
+ }
+
+ /**
+ * Generic method to set term preferences
+ *
+ * @param preferredTerms a {@link java.util.List} object.
+ * @param initialTerms a {@link java.util.List} object.
+ * @param <T> a T object.
+ */
+ public void setPreferredTermsByDto(Collection<TermDto> preferredTerms, TermVocabularyDto vocDto){
+
+ List<TermDto> oldValues = getPreferredTerms(vocDto);
+ for (TermDto term: oldValues){
+ PreferencesUtil.setBooleanValue(getPrefNameByDto(term), false);
+ }
+ TermType type = null;
+ boolean allSameType = true;
+ for(TermDto term : preferredTerms){
+ PreferencesUtil.setBooleanValue(getPrefNameByDto(term), true);
+ if (type == null){
+ type = term.getTermType();
+ }else if (!type.equals(term.getTermType())){
+ allSameType = false;
+ }
+ }
+
+ //if (initialTerms.iterator().hasNext()){
+ clearTermMapForTermVoc(vocDto.getUuid());
+ List<TermDto> list = new ArrayList<>(preferredTerms);
+ cachedTermMap.put(vocDto.getUuid(), list);
+ if (allSameType){
+ cachedTermMap.put(type, list);
+ }
+ //}
+ }
+
- hiddenTermsMap.put(typeName, undesiredTermUuids);
+ public <T extends DefinedTermBase> void clearTermMapForTermVoc(UUID vocUuid){
+ cachedTermMap.remove(vocUuid);
}
+
+ public void clearTermMapForTermType(TermType termType){
+ cachedTermMap.remove(termType);
+ }
+
+ /**
+ * Generic method to get term preferences for a term vocabulary
+ *
+ * @param initialTerms a {@link java.util.List} object.
+ * @return a {@link java.util.List} object.
+ */
+ public <T extends DefinedTermBase> List<T> getTerms(List<UUID> uuidList, Class clazz){
+
+ List<T> filteredTerms = new ArrayList<>();
+ @SuppressWarnings("unchecked")
+ List<T> terms = TermStore.getTerms(clazz);
+ if (uuidList == null || uuidList.isEmpty()){
+ return terms;
+ }
+ for (T term : terms){
+ if(uuidList.contains(term.getUuid())){
+ filteredTerms.add(term);
+ }
+ }
+
+ return filteredTerms;
+ }
+
+ /**
+ * Generic method to get term preferences for a term vocabulary
+ *
+ * @param initialTerms a {@link java.util.List} object.
+ * @return a {@link java.util.List} object.
+ */
+ public <T extends DefinedTermBase> List<T> getTerms(List<UUID> uuidList, TermType type){
+
+ List<T> filteredTerms = new ArrayList<>();
+ List<T> terms = TermStore.getTerms(type, null);
+ if (uuidList == null || uuidList.isEmpty()){
+ return terms;
+ }
+ for (T term : terms){
+ if(uuidList.contains(term.getUuid())){
+ filteredTerms.add(term);
+ }
+ }
+
+ return filteredTerms;
+ }
+
+ /**
+ * Generic method to get term preferences for a term vocabulary
+ *
+ * @param initialTerms a {@link java.util.List} object.
+ * @return a {@link java.util.List} object.
+ */
+ public <T extends DefinedTermBase> List<T> getAllTerms(TermType type, Comparator comparator){
+ List<T> terms = TermStore.getTerms(type, comparator);
+ return terms;
+ }
}