• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 15005414953

13 May 2025 07:48PM UTC coverage: 64.958% (+0.04%) from 64.915%
15005414953

push

github

web-flow
TRUNK-6339: Remove the add demo data option from the setup wizard (#5029)

* TRUNK-6339: Remove the add demo data option from the setup wizard

* remove unused import

* remove demo data liquibase file

0 of 2 new or added lines in 1 file covered. (0.0%)

7 existing lines in 3 files now uncovered.

23344 of 35937 relevant lines covered (64.96%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.54
/api/src/main/java/org/openmrs/Concept.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs;
11

12
import java.io.Serializable;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.Collections;
16
import java.util.Date;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.LinkedHashSet;
20
import java.util.List;
21
import java.util.Locale;
22
import java.util.Map;
23
import java.util.Set;
24
import java.util.TreeSet;
25
import java.util.stream.Collectors;
26

27
import org.apache.commons.lang3.StringUtils;
28
import org.codehaus.jackson.annotate.JsonIgnore;
29
import org.hibernate.envers.Audited;
30
import org.hibernate.search.engine.backend.types.ObjectStructure;
31
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.ValueBridgeRef;
32
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AssociationInverseSide;
33
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId;
34
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
35
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
36
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded;
37
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField;
38
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath;
39
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue;
40
import org.openmrs.annotation.AllowDirectAccess;
41
import org.openmrs.api.APIException;
42
import org.openmrs.api.ConceptNameType;
43
import org.openmrs.api.ConceptService;
44
import org.openmrs.api.context.Context;
45
import org.openmrs.api.db.hibernate.search.bridge.OpenmrsObjectValueBridge;
46
import org.openmrs.customdatatype.CustomValueDescriptor;
47
import org.openmrs.customdatatype.Customizable;
48
import org.openmrs.util.LocaleUtility;
49
import org.openmrs.util.OpenmrsUtil;
50
import org.slf4j.Logger;
51
import org.slf4j.LoggerFactory;
52
import org.springframework.util.ObjectUtils;
53

54
/**
55
 * A Concept object can represent either a question or an answer to a data point. That data point is
56
 * usually an {@link Obs}. <br>
57
 * <br>
58
 * A Concept can have multiple names and multiple descriptions within one locale and across multiple
59
 * locales.<br>
60
 * <br>
61
 * To save a Concept to the database, first build up the Concept object in java, then pass that
62
 * object to the {@link ConceptService}.<br>
63
 * <br>
64
 * To get a Concept that is stored in the database, call a method in the {@link ConceptService} to
65
 * fetch an object. To get child objects off of that Concept, further calls to the
66
 * {@link ConceptService} or the database are not needed. e.g. To get the list of answers that are
67
 * stored to a concept, get the concept, then call {@link Concept#getAnswers()}
68
 * 
69
 * @see ConceptName
70
 * @see ConceptDescription
71
 * @see ConceptAnswer
72
 * @see ConceptSet
73
 * @see ConceptMap
74
 * @see ConceptService
75
 */
76
@Audited
77
public class Concept extends BaseOpenmrsObject implements Auditable, Retireable, Serializable, Attributable<Concept>,Customizable<ConceptAttribute> {
78
        
79
        public static final long serialVersionUID = 57332L;
80
        
81
        private static final Logger log = LoggerFactory.getLogger(Concept.class);
1✔
82
        private static final String CONCEPT_NAME_LOCALE_NULL = "Concept.name.locale.null";
83
        
84
        // Fields
85
        @DocumentId
86
        private Integer conceptId;
87
        
88
        @GenericField
1✔
89
        private Boolean retired = false;
1✔
90
        
91
        private User retiredBy;
92
        
93
        private Date dateRetired;
94
        
95
        private String retireReason;
96
        
97
        @KeywordField(
98
                valueBridge = @ValueBridgeRef(type = OpenmrsObjectValueBridge.class)
99
        )
100
        private ConceptDatatype datatype;
101

102
        @KeywordField(
103
                valueBridge = @ValueBridgeRef(type = OpenmrsObjectValueBridge.class)
104
        )
105
        private ConceptClass conceptClass;
106
        
107
        private Boolean set = false;
1✔
108
        
109
        private String version;
110
        
111
        private User creator;
112
        
113
        private Date dateCreated;
114
        
115
        private User changedBy;
116
        
117
        private Date dateChanged;
118
        
119
        @AllowDirectAccess
120
        @AssociationInverseSide(inversePath = @ObjectPath({@PropertyValue(propertyName = "concept")}))
121
        private Collection<ConceptName> names;
122
        
123
        @AllowDirectAccess
124
        private Collection<ConceptAnswer> answers;
125
        
126
        private Collection<ConceptSet> conceptSets;
127
        
128
        private Collection<ConceptDescription> descriptions;
129
        
130
        @IndexedEmbedded
131
        @AssociationInverseSide(inversePath = @ObjectPath({
132
                @PropertyValue(propertyName = "concept")
133
        }))
134
        private Collection<ConceptMap> conceptMappings;
135
        
136
        /**
137
         * A cache of locales to names which have compatible locales. Built on-the-fly by
138
         * getCompatibleNames().
139
         */
140
        private Map<Locale, List<ConceptName>> compatibleCache;
141

142
        private Set<ConceptAttribute> attributes = new LinkedHashSet<>();
1✔
143

144
        /** default constructor */
145
        public Concept() {
1✔
146
                names = new HashSet<>();
1✔
147
                answers = new HashSet<>();
1✔
148
                conceptSets = new TreeSet<>();
1✔
149
                descriptions = new HashSet<>();
1✔
150
                conceptMappings = new HashSet<>();
1✔
151
        }
1✔
152
        
153
        /**
154
         * Convenience constructor with conceptid to save to {@link #setConceptId(Integer)}. This
155
         * effectively creates a concept stub that can be used to make other calls. Because the
156
         * {@link #equals(Object)} and {@link #hashCode()} methods rely on conceptId, this allows a stub
157
         * to masquerade as a full concept as long as other objects like {@link #getAnswers()} and
158
         * {@link #getNames()} are not needed/called.
159
         * 
160
         * @param conceptId the concept id to set
161
         */
162
        public Concept(Integer conceptId) {
163
                this();
1✔
164
                this.conceptId = conceptId;
1✔
165
        }
1✔
166
        
167
        /**
168
         * @return Returns all answers (including retired answers).
169
         * <strong>Should</strong> return retired and non-retired answers
170
         * <strong>Should</strong> not return null if answers is null or empty
171
         */
172
        public Collection<ConceptAnswer> getAnswers() {
173
                if (answers == null) {
1✔
174
                        answers = new HashSet<>();
1✔
175
                }
176
                return answers;
1✔
177
        }
178
        
179
        /**
180
         * If <code>includeRetired</code> is true, then the returned object is the actual stored list of
181
         * {@link ConceptAnswer}s
182
         * 
183
         * @param includeRetired true/false whether to also include the retired answers
184
         * @return Returns the answers for this Concept
185
         * <strong>Should</strong> return the same as getAnswers() if includeRetired is true
186
         * <strong>Should</strong> not return retired answers if includeRetired is false
187
         */
188
        public Collection<ConceptAnswer> getAnswers(boolean includeRetired) {
189
                if (includeRetired) {
1✔
190
                        return getAnswers();
1✔
191
                } else {
192
                        return getAnswers().stream()
1✔
193
                                        .filter(a -> !a.getAnswerConcept().getRetired())
1✔
194
                                        .collect(Collectors.toSet());
1✔
195
                }
196
        }
197

198
        /**
199
         * Set this Concept as having the given <code>answers</code>; This method assumes that the
200
         * sort_weight has already been set.
201
         * 
202
         * @param answers The answers to set.
203
         */
204
        public void setAnswers(Collection<ConceptAnswer> answers) {
205
                this.answers = answers;
1✔
206
        }
1✔
207
        
208
        /**
209
         * Add the given ConceptAnswer to the list of answers for this Concept
210
         * 
211
         * @param conceptAnswer
212
         * <strong>Should</strong> add the ConceptAnswer to Concept
213
         * <strong>Should</strong> not fail if answers list is null
214
         * <strong>Should</strong> not fail if answers contains ConceptAnswer already
215
         * <strong>Should</strong> set the sort weight to the max plus one if not provided
216
         */
217
        public void addAnswer(ConceptAnswer conceptAnswer) {
218
                if (conceptAnswer != null) {
1✔
219
                        if (!getAnswers().contains(conceptAnswer)) {
1✔
220
                                conceptAnswer.setConcept(this);
1✔
221
                                getAnswers().add(conceptAnswer);
1✔
222
                        }
223
                        
224
                        if ((conceptAnswer.getSortWeight() == null) || (conceptAnswer.getSortWeight() <= 0)) {
1✔
225
                                //find largest sort weight
226
                                ConceptAnswer a = Collections.max(answers);
1✔
227
                                //a.sortWeight can be NULL
228
                                Double sortWeight = (a == null) ? 1d : ((a.getSortWeight() == null) ? 1d : a.getSortWeight() + 1d);
1✔
229
                                conceptAnswer.setSortWeight(sortWeight);
1✔
230
                        }
231
                }
232
        }
1✔
233
        
234
        /**
235
         * Remove the given answer from the list of answers for this Concept
236
         * 
237
         * @param conceptAnswer answer to remove
238
         * @return true if the entity was removed, false otherwise
239
         * <strong>Should</strong> not fail if answers is empty
240
         * <strong>Should</strong> not fail if given answer does not exist in list
241
         */
242
        public boolean removeAnswer(ConceptAnswer conceptAnswer) {
243
                return getAnswers().remove(conceptAnswer);
1✔
244
        }
245
        
246
        /**
247
         * @return Returns the changedBy.
248
         */
249
        @Override
250
        public User getChangedBy() {
251
                return changedBy;
1✔
252
        }
253
        
254
        /**
255
         * @param changedBy The changedBy to set.
256
         */
257
        @Override
258
        public void setChangedBy(User changedBy) {
259
                this.changedBy = changedBy;
1✔
260
        }
1✔
261
        
262
        /**
263
         * @return Returns the conceptClass.
264
         */
265
        public ConceptClass getConceptClass() {
266
                return conceptClass;
1✔
267
        }
268
        
269
        /**
270
         * @param conceptClass The conceptClass to set.
271
         */
272
        public void setConceptClass(ConceptClass conceptClass) {
273
                this.conceptClass = conceptClass;
1✔
274
        }
1✔
275
        
276
        /**
277
         * whether or not this concept is a set
278
         * 
279
         * @deprecated as of 2.0, use {@link #getSet()}
280
         */
281
        @Deprecated
282
        @JsonIgnore
283
        public Boolean isSet() {
284
                return getSet();
×
285
        }
286
        
287
        /**
288
         * @param set whether or not this concept is a set
289
         */
290
        public void setSet(Boolean set) {
291
                this.set = set;
1✔
292
        }
1✔
293
        
294
        public Boolean getSet() {
295
                return set;
1✔
296
        }
297
        
298
        /**
299
         * @return Returns the conceptDatatype.
300
         */
301
        public ConceptDatatype getDatatype() {
302
                return datatype;
1✔
303
        }
304
        
305
        /**
306
         * @param conceptDatatype The conceptDatatype to set.
307
         */
308
        public void setDatatype(ConceptDatatype conceptDatatype) {
309
                this.datatype = conceptDatatype;
1✔
310
        }
1✔
311
        
312
        /**
313
         * @return Returns the conceptId.
314
         */
315
        public Integer getConceptId() {
316
                return conceptId;
1✔
317
        }
318
        
319
        /**
320
         * @param conceptId The conceptId to set.
321
         */
322
        public void setConceptId(Integer conceptId) {
323
                this.conceptId = conceptId;
1✔
324
        }
1✔
325
        
326
        /**
327
         * @return Returns the creator.
328
         */
329
        @Override
330
        public User getCreator() {
331
                return creator;
1✔
332
        }
333
        
334
        /**
335
         * @param creator The creator to set.
336
         */
337
        @Override
338
        public void setCreator(User creator) {
339
                this.creator = creator;
1✔
340
        }
1✔
341
        
342
        /**
343
         * @return Returns the dateChanged.
344
         */
345
        @Override
346
        public Date getDateChanged() {
347
                return dateChanged;
1✔
348
        }
349
        
350
        /**
351
         * @param dateChanged The dateChanged to set.
352
         */
353
        @Override
354
        public void setDateChanged(Date dateChanged) {
355
                this.dateChanged = dateChanged;
1✔
356
        }
1✔
357
        
358
        /**
359
         * @return Returns the dateCreated.
360
         */
361
        @Override
362
        public Date getDateCreated() {
363
                return dateCreated;
1✔
364
        }
365
        
366
        /**
367
         * @param dateCreated The dateCreated to set.
368
         */
369
        @Override
370
        public void setDateCreated(Date dateCreated) {
371
                this.dateCreated = dateCreated;
1✔
372
        }
1✔
373
        
374
        /**
375
         * Sets the preferred name /in this locale/ to the specified conceptName and its Locale, if
376
         * there is an existing preferred name for this concept in the same locale, this one will
377
         * replace the old preferred name. Also, the name is added to the concept if it is not already
378
         * among the concept names.
379
         * 
380
         * @param preferredName The name to be marked as preferred in its locale
381
         * <strong>Should</strong> only allow one preferred name
382
         * <strong>Should</strong> add the name to the list of names if it not among them before
383
         * <strong>Should</strong> fail if the preferred name to set to is an index term
384
         */
385
        public void setPreferredName(ConceptName preferredName) {
386
                
387
                if (preferredName == null || preferredName.getVoided() || preferredName.isIndexTerm()) {
1✔
388
                        throw new APIException("Concept.error.preferredName.null", (Object[]) null);
1✔
389
                } else if (preferredName.getLocale() == null) {
1✔
390
                        throw new APIException(CONCEPT_NAME_LOCALE_NULL, (Object[]) null);
×
391
                }
392
                
393
                //first revert the current preferred name(if any) from being preferred
394
                ConceptName oldPreferredName = getPreferredName(preferredName.getLocale(), true);
1✔
395
                if (oldPreferredName != null) {
1✔
396
                        oldPreferredName.setLocalePreferred(false);
1✔
397
                }
398
                
399
                preferredName.setLocalePreferred(true);
1✔
400
                //add this name, if it is new or not among this concept's names
401
                if (preferredName.getConceptNameId() == null || !getNames().contains(preferredName)) {
1✔
402
                        addName(preferredName);
1✔
403
                }
404
        }
1✔
405
        
406
        /**
407
         * A convenience method to get the concept-name (if any) which has a particular tag. This does
408
         * not guarantee that the returned name is the only one with the tag.
409
         * 
410
         * @param conceptNameTag the tag for which to look
411
         * @return the tagged name, or null if no name has the tag
412
         */
413
        public ConceptName findNameTaggedWith(ConceptNameTag conceptNameTag) {
414
                ConceptName taggedName = null;
×
415
                for (ConceptName possibleName : getNames()) {
×
416
                        if (possibleName.hasTag(conceptNameTag)) {
×
417
                                taggedName = possibleName;
×
418
                                break;
×
419
                        }
420
                }
×
421
                return taggedName;
×
422
        }
423
        
424
        /**
425
         * Returns a name in the given locale. If a name isn't found with an exact match, a compatible
426
         * locale match is returned. If no name is found matching either of those, the first name
427
         * defined for this concept is returned.
428
         * 
429
         * @param locale the locale to fetch for
430
         * @return ConceptName attributed to the Concept in the given locale
431
         * @since 1.5
432
         * @see Concept#getNames(Locale) to get all the names for a locale,
433
         * @see Concept#getPreferredName(Locale) for the preferred name (if any)
434
         */
435
        public ConceptName getName(Locale locale) {
436
                return getName(locale, false);
1✔
437
        }
438
        
439
        /**
440
         * Returns concept name, the look up for the appropriate name is done in the following order;
441
         * <ul>
442
         * <li>First name found in any locale that is explicitly marked as preferred while searching
443
         * available locales in order of preference (the locales are traversed in their order as they
444
         * are listed in the 'locale.allowed.list' including english global property).</li>
445
         * <li>First "Fully Specified" name found while searching available locales in order of
446
         * preference.</li>
447
         * <li>The first fully specified name found while searching through all names for the concept</li>
448
         * <li>The first synonym found while searching through all names for the concept.</li>
449
         * <li>The first random name found(except index terms) while searching through all names.</li>
450
         * </ul>
451
         * 
452
         * @return {@link ConceptName} in the current locale or any locale if none found
453
         * @since 1.5
454
         * @see Concept#getNames(Locale) to get all the names for a locale
455
         * @see Concept#getPreferredName(Locale) for the preferred name (if any)
456
         * <strong>Should</strong> return the name explicitly marked as locale preferred if any is present
457
         * <strong>Should</strong> return the fully specified name in a locale if no preferred name is set
458
         * <strong>Should</strong> return null if the only added name is an index term
459
         * <strong>Should</strong> return name in broader locale in case none is found in specific one
460
         */
461
        public ConceptName getName() {
462
                if (getNames().isEmpty()) {
1✔
463
                        log.debug("there are no names defined for: {}", conceptId);
1✔
464
                        return null;
1✔
465
                }
466
                
467
                for (Locale currentLocale : LocaleUtility.getLocalesInOrder()) {
1✔
468
                        ConceptName preferredName = getPreferredName(currentLocale);
1✔
469
                        if (preferredName != null) {
1✔
470
                                return preferredName;
1✔
471
                        }
472
                        
473
                        ConceptName fullySpecifiedName = getFullySpecifiedName(currentLocale);
1✔
474
                        if (fullySpecifiedName != null) {
1✔
475
                                return fullySpecifiedName;
×
476
                        }
477
                        
478
                        //if the locale has an variants e.g en_GB, try names in the locale excluding the country code i.e en
479
                        if (!StringUtils.isBlank(currentLocale.getCountry()) || !StringUtils.isBlank(currentLocale.getVariant())) {
1✔
480
                                Locale broaderLocale = new Locale(currentLocale.getLanguage());
1✔
481
                                ConceptName prefNameInBroaderLoc = getPreferredName(broaderLocale);
1✔
482
                                if (prefNameInBroaderLoc != null) {
1✔
483
                                        return prefNameInBroaderLoc;
1✔
484
                                }
485
                                
486
                                ConceptName fullySpecNameInBroaderLoc = getFullySpecifiedName(broaderLocale);
1✔
487
                                if (fullySpecNameInBroaderLoc != null) {
1✔
488
                                        return fullySpecNameInBroaderLoc;
×
489
                                }
490
                        }
491
                }
1✔
492
                
493
                for (ConceptName cn : getNames()) {
1✔
494
                        if (cn.isFullySpecifiedName()) {
1✔
495
                                return cn;
1✔
496
                        }
497
                }
1✔
498
                
499
                if (!getSynonyms().isEmpty()) {
1✔
500
                        return getSynonyms().iterator().next();
×
501
                }
502
                
503
                // we don't expect to get here since every concept name must have at least
504
                // one fully specified name, but just in case (probably inconsistent data)
505
                
506
                return null;
1✔
507
        }
508
        
509
        /**
510
         * Checks whether this concept has the given string in any of the names in the given locale
511
         * already.
512
         * 
513
         * @param name the ConceptName.name to compare to
514
         * @param locale the locale to look in (null to check all locales)
515
         * @return true/false whether the name exists already
516
         * <strong>Should</strong> return false if name is null
517
         * <strong>Should</strong> return true if locale is null but name exists
518
         * <strong>Should</strong> return false if locale is null but name does not exist
519
         */
520
        public boolean hasName(String name, Locale locale) {
521
                if (name == null) {
1✔
522
                        return false;
1✔
523
                }
524
                
525
                Collection<ConceptName> currentNames;
526
                if (locale == null) {
1✔
527
                        currentNames = getNames();
1✔
528
                } else {
529
                        currentNames = getNames(locale);
1✔
530
                }
531
                
532
                for (ConceptName currentName : currentNames) {
1✔
533
                        if (name.equalsIgnoreCase(currentName.getName())) {
1✔
534
                                return true;
1✔
535
                        }
536
                }
1✔
537
                
538
                return false;
1✔
539
        }
540
        
541
        /**
542
         * Returns concept name depending of locale, type (short, fully specified, etc) and tag.
543
         * Searches in the locale, and then the locale's parent if nothing is found.
544
         * 
545
         * @param ofType find a name of this type (optional)
546
         * @param havingTag find a name with this tag (optional)
547
         * @param locale find a name with this locale (required)
548
         * @return a name that matches the arguments, or null if none is found. If there are multiple
549
         *         matches and one is locale_preferred, that will be returned, otherwise a random one of
550
         *         the matches will be returned.
551
         * @since 1.9
552
         **/
553
        public ConceptName getName(Locale locale, ConceptNameType ofType, ConceptNameTag havingTag) {
554
                Collection<ConceptName> namesInLocale = getNames(locale);
×
555
                if (!namesInLocale.isEmpty()) {
×
556
                        //Pass the possible candidates through a stream and save the ones that match requirements to the list
557
                        List<ConceptName> matches = namesInLocale.stream().filter(
×
558
                                c->(ofType==null || ofType.equals(c.getConceptNameType())) && (havingTag==null || c.hasTag(havingTag))
×
559
                        ).collect(Collectors.toList());
×
560
                        
561
                        // if we have any matches, we'll return one of them
562
                        if (matches.size() == 1) {
×
563
                                return matches.get(0);
×
564
                        } else if (matches.size() > 1) {
×
565
                                for (ConceptName match : matches) {
×
566
                                        if (match.getLocalePreferred()) {
×
567
                                                return match;
×
568
                                        }
569
                                }
×
570
                                // none was explicitly marked as preferred
571
                                return matches.get(0);
×
572
                        }
573
                }
574
                
575
                // if we reach here, there were no matching names, so try to look in the parent locale
576
                Locale parent = new Locale(locale.getLanguage());
×
577
                if (!parent.equals(locale)) {
×
578
                        return getName(parent, ofType, havingTag);
×
579
                } else {
580
                        return null;
×
581
                }
582
        }
583
        
584
        /**
585
         * Returns a name in the given locale. If a name isn't found with an exact match, a compatible
586
         * locale match is returned. If no name is found matching either of those, the first name
587
         * defined for this concept is returned.
588
         * 
589
         * @param locale the language and country in which the name is used
590
         * @param exact true/false to return only exact locale (no default locale)
591
         * @return the closest name in the given locale, or the first name
592
         * @see Concept#getNames(Locale) to get all the names for a locale,
593
         * @see Concept#getPreferredName(Locale) for the preferred name (if any)
594
         * <strong>Should</strong> return exact name locale match given exact equals true
595
         * <strong>Should</strong> return loose match given exact equals false
596
         * <strong>Should</strong> return null if no names are found in locale given exact equals true
597
         * <strong>Should</strong> return any name if no locale match given exact equals false
598
         * <strong>Should</strong> return name in broader locale in case none is found in specific one
599
         */
600
        public ConceptName getName(Locale locale, boolean exact) {
601
                
602
                // fail early if this concept has no names defined
603
                if (getNames().isEmpty()) {
1✔
604
                        log.debug("there are no names defined for: {}", conceptId);
1✔
605
                        return null;
1✔
606
                }
607
                
608
                log.debug("Getting conceptName for locale: {}", locale);
1✔
609
                
610
                ConceptName exactName = getNameInLocale(locale);
1✔
611
                
612
                if (exactName != null) {
1✔
613
                        return exactName;
1✔
614
                }
615
                
616
                if (!exact) {
1✔
617
                        Locale broaderLocale = new Locale(locale.getLanguage());
1✔
618
                        ConceptName name = getNameInLocale(broaderLocale);
1✔
619
                        return name != null ? name : getName();
1✔
620
                }
621
                return null;
1✔
622
        }
623
        
624
        /**
625
         * Gets the best name in the specified locale.
626
         * 
627
         * @param locale
628
         * @return null if name in given locale doesn't exist
629
         */
630
        private ConceptName getNameInLocale(Locale locale) {
631
                ConceptName preferredName = getPreferredName(locale);
1✔
632
                if (preferredName != null) {
1✔
633
                        return preferredName;
1✔
634
                }
635
                
636
                ConceptName fullySpecifiedName = getFullySpecifiedName(locale);
1✔
637
                if (fullySpecifiedName != null) {
1✔
638
                        return fullySpecifiedName;
×
639
                } else if (!getSynonyms(locale).isEmpty()) {
1✔
640
                        return getSynonyms(locale).iterator().next();
1✔
641
                }
642
                
643
                return null;
1✔
644
        }
645
        
646
        public ConceptName getPreferredName(Locale forLocale) {
647
                return getPreferredName(forLocale, false);
1✔
648
        }
649
        
650
        /**
651
         * Returns the name which is explicitly marked as preferred for a given locale.
652
         * 
653
         * @param forLocale locale for which to return a preferred name
654
         * @return preferred name for the locale, or null if no preferred name is specified
655
         * <strong>Should</strong> return the concept name explicitly marked as locale preferred
656
         * <strong>Should</strong> return the concept name marked as locale preferred a partial match locale (same language but different country) if no exact match and exact set to false
657
         * <strong>Should</strong> return the fully specified name if no name is explicitly marked as locale preferred and exact set to false
658
         */
659
        public ConceptName getPreferredName(Locale forLocale, Boolean exact) {
660
                
661
                if (log.isDebugEnabled()) {
1✔
662
                        log.debug("Getting preferred conceptName for locale: " + forLocale);
×
663
                }
664
                
665
                if (forLocale == null) {
1✔
666
                        log.warn("Locale cannot be null");
×
667
                        return null;
×
668
                }
669
                
670
                for (ConceptName nameInLocale : getNames(forLocale)) {
1✔
671
                        if (ObjectUtils.nullSafeEquals(nameInLocale.getLocalePreferred(), true)) {
1✔
672
                                return nameInLocale;
1✔
673
                        }
674
                }
1✔
675
                
676
                if (exact) {
1✔
677
                        return null;
1✔
678
                } else {
679
                        // look for partially locale match - any language matches takes precedence over country matches.
680
                        ConceptName bestMatch = null;
1✔
681

682
                        for (ConceptName nameInLocale : getPartiallyCompatibleNames(forLocale)) {
1✔
683
                                if (ObjectUtils.nullSafeEquals(nameInLocale.getLocalePreferred(), true)) {
1✔
684
                                        Locale nameLocale = nameInLocale.getLocale();
1✔
685
                                        if (forLocale.getLanguage().equals(nameLocale.getLanguage())) {
1✔
686
                                                return nameInLocale;
1✔
687
                                        } else {
688
                                                bestMatch = nameInLocale;
×
689
                                        }
690

691
                                }
692
                        }
1✔
693

694
                        if (bestMatch != null) {
1✔
695
                                return bestMatch;
×
696
                        }
697

698
                        return getFullySpecifiedName(forLocale);
1✔
699
                }
700
        }
701
        
702
        /**
703
         * Convenience method that returns the fully specified name in the locale
704
         * 
705
         * @param locale locale from which to look up the fully specified name
706
         * @return the name explicitly marked as fully specified for the locale
707
         * <strong>Should</strong> return the name marked as fully specified for the given locale
708
         */
709
        public ConceptName getFullySpecifiedName(Locale locale) {
710
                if (locale != null && !getNames(locale).isEmpty()) {
1✔
711
                        //get the first fully specified name, since every concept must have a fully specified name,
712
                        //then, this loop will have to return a name
713
                        for (ConceptName conceptName : getNames(locale)) {
1✔
714
                                if (ObjectUtils.nullSafeEquals(conceptName.isFullySpecifiedName(), true)) {
1✔
715
                                        return conceptName;
1✔
716
                                }
717
                        }
1✔
718
                        
719
                        // look for partially locale match - any language matches takes precedence over country matches.
720
                        ConceptName bestMatch = null;
1✔
721
                        for (ConceptName conceptName : getPartiallyCompatibleNames(locale)) {
1✔
722
                                if (ObjectUtils.nullSafeEquals(conceptName.isFullySpecifiedName(), true)) {
1✔
723
                                        Locale nameLocale = conceptName.getLocale();
1✔
724
                                        if (locale.getLanguage().equals(nameLocale.getLanguage())) {
1✔
725
                                                return conceptName;
1✔
726
                                        }
727
                                        bestMatch = conceptName;
×
728
                                }
729
                        }
1✔
730
                        return bestMatch;
1✔
731
                        
732
                }
733
                return null;
1✔
734
        }
735
        
736
        /**
737
         * Returns all names available in a specific locale. <br>
738
         * <br>
739
         * This is recommended when managing the concept dictionary.
740
         * 
741
         * @param locale locale for which names should be returned
742
         * @return Collection of ConceptNames with the given locale
743
         */
744
        public Collection<ConceptName> getNames(Locale locale) {
745
                return getNames().stream()
1✔
746
                                .filter(n -> n.getLocale().equals(locale))
1✔
747
                                .collect(Collectors.toSet());
1✔
748
        }
749
        
750
        /**
751
         * Returns all names available for locale language "or" country. <br>
752
         * <br>
753
         * 
754
         * @param locale locale for which names should be returned
755
         * @return Collection of ConceptNames with the given locale language or country
756
         */
757
        private Collection<ConceptName> getPartiallyCompatibleNames(Locale locale) {
758
                String language = locale.getLanguage();
1✔
759
                String country = locale.getCountry();
1✔
760
                
761
                return getNames().stream()
1✔
762
                                .filter(n -> language.equals(n.getLocale().getLanguage()) || 
1✔
763
                                                        StringUtils.isNotBlank(country) && country.equals(n.getLocale().getCountry()))
1✔
764
                                .collect(Collectors.toSet());
1✔
765
        }
766
        
767
        /**
768
         * Returns all names from compatible locales. A locale is considered compatible if it is exactly
769
         * the same locale, or if either locale has no country specified and the language matches. <br>
770
         * <br>
771
         * This is recommended when presenting possible names to the use.
772
         * 
773
         * @param desiredLocale locale with which the names should be compatible
774
         * @return Collection of compatible names
775
         * <strong>Should</strong> exclude incompatible country locales
776
         * <strong>Should</strong> exclude incompatible language locales
777
         */
778
        public List<ConceptName> getCompatibleNames(Locale desiredLocale) {
779
                // lazy create the cache
780
                List<ConceptName> compatibleNames = null;
1✔
781
                if (compatibleCache == null) {
1✔
782
                        compatibleCache = new HashMap<>();
1✔
783
                } else {
784
                        compatibleNames = compatibleCache.get(desiredLocale);
×
785
                }
786
                
787
                if (compatibleNames == null) {
1✔
788
                        compatibleNames = new ArrayList<>();
1✔
789
                        for (ConceptName possibleName : getNames()) {
1✔
790
                                if (LocaleUtility.areCompatible(possibleName.getLocale(), desiredLocale)) {
1✔
791
                                        compatibleNames.add(possibleName);
1✔
792
                                }
793
                        }
1✔
794
                        compatibleCache.put(desiredLocale, compatibleNames);
1✔
795
                }
796
                return compatibleNames;
1✔
797
        }
798
        
799
        /**
800
         * Sets the specified name as the fully specified name for the locale and the current fully
801
         * specified (if any) ceases to be the fully specified name for the locale.
802
         * 
803
         * @param fullySpecifiedName the new fully specified name to set
804
         * <strong>Should</strong> set the concept name type of the specified name to fully specified
805
         * <strong>Should</strong> convert the previous fully specified name if any to a synonym
806
         * <strong>Should</strong> add the name to the list of names if it not among them before
807
         */
808
        public void setFullySpecifiedName(ConceptName fullySpecifiedName) {
809
                if (fullySpecifiedName == null || fullySpecifiedName.getLocale() == null) {
1✔
810
                        throw new APIException(CONCEPT_NAME_LOCALE_NULL, (Object[]) null);
×
811
                } else if (fullySpecifiedName.getVoided()) {
1✔
812
                        throw new APIException("Concept.error.fullySpecifiedName.null", (Object[]) null);
×
813
                }
814
                
815
                ConceptName oldFullySpecifiedName = getFullySpecifiedName(fullySpecifiedName.getLocale());
1✔
816
                if (oldFullySpecifiedName != null) {
1✔
817
                        oldFullySpecifiedName.setConceptNameType(null);
1✔
818
                }
819
                fullySpecifiedName.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
1✔
820
                //add this name, if it is new or not among this concept's names
821
                if (fullySpecifiedName.getConceptNameId() == null || !getNames().contains(fullySpecifiedName)) {
1✔
822
                        addName(fullySpecifiedName);
1✔
823
                }
824
        }
1✔
825
        
826
        /**
827
         * Sets the specified name as the short name for the locale and the current shortName(if any)
828
         * ceases to be the short name for the locale.
829
         * 
830
         * @param shortName the new shortName to set
831
         * <strong>Should</strong> set the concept name type of the specified name to short
832
         * <strong>Should</strong> convert the previous shortName if any to a synonym
833
         * <strong>Should</strong> add the name to the list of names if it not among them before
834
         * <strong>Should</strong> void old short name if new one is blank (do not save blanks!)
835
         */
836
        public void setShortName(ConceptName shortName) {
837
                if (shortName != null) {
1✔
838
                        if (shortName.getLocale() == null) {
1✔
839
                                throw new APIException(CONCEPT_NAME_LOCALE_NULL, (Object[]) null);
×
840
                        }
841
                        ConceptName oldShortName = getShortNameInLocale(shortName.getLocale());
1✔
842
                        if (oldShortName != null) {
1✔
843
                                oldShortName.setConceptNameType(null);
1✔
844
                        }
845
                        shortName.setConceptNameType(ConceptNameType.SHORT);
1✔
846
                        if (StringUtils.isNotBlank(shortName.getName())
1✔
847
                                && (shortName.getConceptNameId() == null || !getNames().contains(shortName))) {
1✔
848
                                //add this name, if it is new or not among this concept's names
849
                                addName(shortName);
1✔
850
                        }
851
                } else {
1✔
852
                        throw new APIException("Concept.error.shortName.null", (Object[]) null);
×
853
                }
854
        }
1✔
855
        
856
        /**
857
         * Gets the explicitly specified short name for a locale.
858
         * 
859
         * @param locale locale for which to find a short name
860
         * @return the short name, or null if none has been explicitly set
861
         */
862
        public ConceptName getShortNameInLocale(Locale locale) {
863
                ConceptName bestMatch = null;
1✔
864
                if (locale != null && !getShortNames().isEmpty()) {
1✔
865
                        for (ConceptName shortName : getShortNames()) {
1✔
866
                                Locale nameLocale = shortName.getLocale();
1✔
867
                                if (nameLocale.equals(locale)) {
1✔
868
                                        return shortName;
1✔
869
                                }
870
                                // test for partially locale match - any language matches takes precedence over country matches.
871
                                if (OpenmrsUtil.nullSafeEquals(locale.getLanguage(), nameLocale.getLanguage())) {
1✔
872
                                        bestMatch = shortName;
1✔
873
                                } else if (bestMatch == null && StringUtils.isNotBlank(locale.getCountry())
1✔
UNCOV
874
                                        && locale.getCountry().equals(nameLocale.getCountry())) {
×
875
                                        bestMatch = shortName;
×
876
                                }
877
                        }
1✔
878
                }
879
                return bestMatch;
1✔
880
        }
881
        
882
        /**
883
         * Gets a collection of short names for this concept from all locales.
884
         * 
885
         * @return a collection of all short names for this concept
886
         */
887
        public Collection<ConceptName> getShortNames() {
888
                List<ConceptName> shortNames = new ArrayList<>();
1✔
889
                if (getNames().isEmpty()) {
1✔
890
                        if (log.isDebugEnabled()) {
×
891
                                log.debug("The Concept with id: " + conceptId + " has no names");
×
892
                        }
893
                } else {
894
                        shortNames = getNames().stream()
1✔
895
                                                        .filter(ConceptName::isShort)
1✔
896
                                                        .collect(Collectors.toList());
1✔
897
                }
898
                return shortNames;
1✔
899
        }
900
        
901
        /**
902
         * Returns the short form name for a locale, or if none has been identified, the shortest name
903
         * available in the locale. If exact is false, the shortest name from any locale is returned
904
         * 
905
         * @param locale the language and country in which the short name is used
906
         * @param exact true/false to return only exact locale (no default locale)
907
         * @return the appropriate short name, or null if not found
908
         * <strong>Should</strong> return the name marked as the shortName for the locale if it is present
909
         * <strong>Should</strong> return the shortest name in a given locale for a concept if exact is true
910
         * <strong>Should</strong> return the shortest name for the concept from any locale if exact is false
911
         * <strong>Should</strong> return null if there are no names in the specified locale and exact is true
912
         */
913
        public ConceptName getShortestName(Locale locale, Boolean exact) {
914
                if (log.isDebugEnabled()) {
1✔
915
                        log.debug("Getting shortest conceptName for locale: " + locale);
×
916
                }
917
                
918
                ConceptName shortNameInLocale = getShortNameInLocale(locale);
1✔
919
                if (shortNameInLocale != null) {
1✔
920
                        return shortNameInLocale;
1✔
921
                }
922
                
923
                ConceptName shortestNameForLocale = null;
1✔
924
                ConceptName shortestNameForConcept = null;
1✔
925
                
926
                if (locale != null) {
1✔
927
                        for (ConceptName possibleName : getNames()) {
1✔
928
                                if (possibleName.getLocale().equals(locale)
1✔
929
                                        && ((shortestNameForLocale == null) || (possibleName.getName().length() < shortestNameForLocale
1✔
930
                                                .getName().length()))) {
1✔
931
                                        shortestNameForLocale = possibleName;
1✔
932
                                }
933
                                if ((shortestNameForConcept == null)
1✔
934
                                        || (possibleName.getName().length() < shortestNameForConcept.getName().length())) {
1✔
935
                                        shortestNameForConcept = possibleName;
1✔
936
                                }
937
                        }
1✔
938
                }
939
                
940
                if (exact) {
1✔
941
                        if (shortestNameForLocale == null) {
1✔
942
                                log.warn("No short concept name found for concept id " + conceptId + " for locale "
1✔
943
                                        + locale.getDisplayName());
1✔
944
                        }
945
                        return shortestNameForLocale;
1✔
946
                }
947
                
948
                return shortestNameForConcept;
1✔
949
        }
950
        
951
        /**
952
         * @param name A name
953
         * @return whether this concept has the given name in any locale
954
         */
955
        public boolean isNamed(String name) {
956
                return getNames().stream().anyMatch(cn -> name.equals(cn.getName()));
1✔
957
        }
958
        
959
        /**
960
         * Gets the list of all non-retired concept names which are index terms for this concept
961
         * 
962
         * @return a collection of concept names which are index terms for this concept
963
         * @since 1.7
964
         */
965
        public Collection<ConceptName> getIndexTerms() {
966
                return getNames().stream()
×
967
                                .filter(ConceptName::isIndexTerm)
×
968
                                .collect(Collectors.toSet());                
×
969
        }
970
        
971
        /**
972
         * Gets the list of all non-retired concept names which are index terms in a given locale
973
         * 
974
         * @param locale the locale for the index terms to return
975
         * @return a collection of concept names which are index terms in the given locale
976
         * @since 1.7
977
         */
978
        public Collection<ConceptName> getIndexTermsForLocale(Locale locale) {
979
                return getIndexTerms().stream()
×
980
                                .filter(n -> n.getLocale().equals(locale))
×
981
                        .collect(Collectors.toList());
×
982
        }
983
        
984
        /**
985
         * @return Returns the names.
986
         */
987
        public Collection<ConceptName> getNames() {
988
                return getNames(false);
1✔
989
        }
990
        
991
        /**
992
         * @return Returns the names.
993
         * @param includeVoided Include voided ConceptNames if true.
994
         */
995
        public Collection<ConceptName> getNames(boolean includeVoided) {
996
                if (names == null) {
1✔
997
                        names = new HashSet<>();
×
998
                }
999

1000
                return names.stream()
1✔
1001
                                .filter(n -> includeVoided || !n.getVoided())
1✔
1002
                                .collect(Collectors.toSet());
1✔
1003
        }
1004
        
1005
        /**
1006
         * @param names The names to set.
1007
         */
1008
        public void setNames(Collection<ConceptName> names) {
1009
                this.names = names;
1✔
1010
        }
1✔
1011
        
1012
        /**
1013
         * Add the given ConceptName to the list of names for this Concept
1014
         * 
1015
         * @param conceptName
1016
         * <strong>Should</strong> replace the old preferred name with a current one
1017
         * <strong>Should</strong> replace the old fully specified name with a current one
1018
         * <strong>Should</strong> replace the old short name with a current one
1019
         * <strong>Should</strong> mark the first name added as fully specified
1020
         */
1021
        public void addName(ConceptName conceptName) {
1022
                if (conceptName != null) {
1✔
1023
                        conceptName.setConcept(this);
1✔
1024
                        if (names == null) {
1✔
1025
                                names = new HashSet<>();
×
1026
                        }
1027
                        if (!names.contains(conceptName)) {
1✔
1028
                                if (getNames().isEmpty()
1✔
1029
                                        && !ConceptNameType.FULLY_SPECIFIED.equals(conceptName.getConceptNameType())) {
1✔
1030
                                        conceptName.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
1✔
1031
                                } else {
1032
                                        if (conceptName.isPreferred() && !conceptName.isIndexTerm() && conceptName.getLocale() != null) {
1✔
1033
                                                ConceptName prefName = getPreferredName(conceptName.getLocale(), true);
1✔
1034
                                                if (prefName != null) {
1✔
1035
                                                        prefName.setLocalePreferred(false);
1✔
1036
                                                }
1037
                                        }
1038
                                        if (conceptName.isFullySpecifiedName() && conceptName.getLocale() != null) {
1✔
1039
                                                ConceptName fullySpecName = getFullySpecifiedName(conceptName.getLocale());
1✔
1040
                                                if (fullySpecName != null) {
1✔
1041
                                                        fullySpecName.setConceptNameType(null);
1✔
1042
                                                }
1043
                                        } else if (conceptName.isShort() && conceptName.getLocale() != null) {
1✔
1044
                                                ConceptName shortName = getShortNameInLocale(conceptName.getLocale());
1✔
1045
                                                if (shortName != null) {
1✔
1046
                                                        shortName.setConceptNameType(null);
×
1047
                                                }
1048
                                        }
1049
                                }
1050
                                names.add(conceptName);
1✔
1051
                                if (compatibleCache != null) {
1✔
1052
                                        // clear the locale cache, forcing it to be rebuilt
1053
                                        compatibleCache.clear();
×
1054
                                }
1055
                        }
1056
                }
1057
        }
1✔
1058
        
1059
        /**
1060
         * Remove the given name from the list of names for this Concept
1061
         * 
1062
         * @param conceptName
1063
         * @return true if the entity was removed, false otherwise
1064
         */
1065
        public boolean removeName(ConceptName conceptName) {
1066
                if (names != null) {
1✔
1067
                        return names.remove(conceptName);
1✔
1068
                } else {
1069
                        return false;
×
1070
                }
1071
        }
1072
        
1073
        /**
1074
         * Finds the description of the concept using the current locale in Context.getLocale(). Returns
1075
         * null if none found.
1076
         * 
1077
         * @return ConceptDescription attributed to the Concept in the given locale
1078
         */
1079
        public ConceptDescription getDescription() {
1080
                return getDescription(Context.getLocale());
1✔
1081
        }
1082
        
1083
        /**
1084
         * Finds the description of the concept in the given locale. Returns null if none found.
1085
         * 
1086
         * @param locale
1087
         * @return ConceptDescription attributed to the Concept in the given locale
1088
         */
1089
        public ConceptDescription getDescription(Locale locale) {
1090
                return getDescription(locale, false);
1✔
1091
        }
1092
        
1093
        /**
1094
         * Returns the preferred description for a locale.
1095
         * 
1096
         * @param locale the language and country in which the description is used
1097
         * @param exact true/false to return only exact locale (no default locale)
1098
         * @return the appropriate description, or null if not found
1099
         * <strong>Should</strong> return match on locale exactly
1100
         * <strong>Should</strong> return match on language only
1101
         * <strong>Should</strong> not return match on language only if exact match exists
1102
         * <strong>Should</strong> not return language only match for exact matches
1103
         */
1104
        public ConceptDescription getDescription(Locale locale, boolean exact) {
1105
                log.debug("Getting ConceptDescription for locale: " + locale);
1✔
1106
                
1107
                ConceptDescription foundDescription = null;
1✔
1108
                
1109
                if (locale == null) {
1✔
1110
                        locale = LocaleUtility.getDefaultLocale();
×
1111
                }
1112
                
1113
                Locale desiredLocale = locale;
1✔
1114
                
1115
                ConceptDescription defaultDescription = null;
1✔
1116
                for (ConceptDescription availableDescription : getDescriptions()) {
1✔
1117
                        Locale availableLocale = availableDescription.getLocale();
1✔
1118
                        if (availableLocale.equals(desiredLocale)) {
1✔
1119
                                foundDescription = availableDescription;
1✔
1120
                                // skip out now because we found an exact locale match
1121
                                break;
1✔
1122
                        }
1123
                        if (!exact && LocaleUtility.areCompatible(availableLocale, desiredLocale)) {
1✔
1124
                                foundDescription = availableDescription;
1✔
1125
                        }
1126
                        if (availableLocale.equals(LocaleUtility.getDefaultLocale())) {
1✔
1127
                                defaultDescription = availableDescription;
×
1128
                        }
1129
                }
1✔
1130
                
1131
                if (foundDescription == null) {
1✔
1132
                        // no description with the given locale was found.
1133
                        // return null if exact match desired
1134
                        if (exact) {
1✔
1135
                                log.debug("No concept description found for concept id " + conceptId + " for locale "
1✔
1136
                                        + desiredLocale.toString());
1✔
1137
                        } else {
1138
                                // returning default description locale ("en") if exact match
1139
                                // not desired
1140
                                if (defaultDescription == null) {
1✔
1141
                                        log.debug("No concept description found for default locale for concept id " + conceptId);
1✔
1142
                                } else {
1143
                                        foundDescription = defaultDescription;
×
1144
                                }
1145
                        }
1146
                }
1147
                return foundDescription;
1✔
1148
        }
1149
        
1150
        /**
1151
         * @return the retiredBy
1152
         */
1153
        @Override
1154
        public User getRetiredBy() {
1155
                return retiredBy;
1✔
1156
        }
1157
        
1158
        /**
1159
         * @param retiredBy the retiredBy to set
1160
         */
1161
        @Override
1162
        public void setRetiredBy(User retiredBy) {
1163
                this.retiredBy = retiredBy;
1✔
1164
        }
1✔
1165
        
1166
        /**
1167
         * @return the dateRetired
1168
         */
1169
        @Override
1170
        public Date getDateRetired() {
1171
                return dateRetired;
1✔
1172
        }
1173
        
1174
        /**
1175
         * @param dateRetired the dateRetired to set
1176
         */
1177
        @Override
1178
        public void setDateRetired(Date dateRetired) {
1179
                this.dateRetired = dateRetired;
1✔
1180
        }
1✔
1181
        
1182
        /**
1183
         * @return the retireReason
1184
         */
1185
        @Override
1186
        public String getRetireReason() {
1187
                return retireReason;
1✔
1188
        }
1189
        
1190
        /**
1191
         * @param retireReason the retireReason to set
1192
         */
1193
        @Override
1194
        public void setRetireReason(String retireReason) {
1195
                this.retireReason = retireReason;
1✔
1196
        }
1✔
1197
        
1198
        /**
1199
         * @return Returns the descriptions.
1200
         */
1201
        public Collection<ConceptDescription> getDescriptions() {
1202
                if (descriptions == null) {
1✔
1203
                        descriptions = new HashSet<>();
1✔
1204
                }
1205
                return descriptions;
1✔
1206
        }
1207
        
1208
        /**
1209
         * Sets the collection of descriptions for this Concept.
1210
         * 
1211
         * @param descriptions the collection of descriptions
1212
         */
1213
        public void setDescriptions(Collection<ConceptDescription> descriptions) {
1214
                this.descriptions = descriptions;
1✔
1215
        }
1✔
1216
        
1217
        /**
1218
         * Add the given description to the list of descriptions for this Concept
1219
         * 
1220
         * @param description the description to add
1221
         */
1222
        public void addDescription(ConceptDescription description) {
1223
                if (description != null && StringUtils.isNotBlank(description.getDescription()) && !descriptions.contains(description)) {
1✔
1224
                        description.setConcept(this);
1✔
1225
                        descriptions.add(description);
1✔
1226
                }
1227
        }
1✔
1228
        
1229
        /**
1230
         * Remove the given description from the list of descriptions for this Concept
1231
         * 
1232
         * @param description the description to remove
1233
         * @return true if the entity was removed, false otherwise
1234
         * <strong>Should</strong> should remove description passed from list of descriptions
1235
         */
1236
        public boolean removeDescription(ConceptDescription description) {
1237
                return descriptions.remove(description);
1✔
1238
        }
1239
        
1240
        /**
1241
         * @return Returns the retired.
1242
         * 
1243
         * @deprecated as of 2.0, use {@link #getRetired()}
1244
         */
1245
        @Override
1246
        @Deprecated
1247
        @JsonIgnore
1248
        public Boolean isRetired() {
1249
                return getRetired();
1✔
1250
        }
1251
        
1252
        /**
1253
         * This method delegates to {@link #isRetired()}. This is only needed for jstl syntax like
1254
         * ${concept.retired} because the return type is a Boolean object instead of a boolean
1255
         * primitive type.
1256
         * 
1257
         * @see org.openmrs.Retireable#isRetired()
1258
         */
1259
        @Override
1260
        public Boolean getRetired() {
1261
                return retired;
1✔
1262
        }
1263
        
1264
        /**
1265
         * @param retired The retired to set.
1266
         */
1267
        @Override
1268
        public void setRetired(Boolean retired) {
1269
                this.retired = retired;
1✔
1270
        }
1✔
1271
        
1272
        /**
1273
         * Gets the synonyms in the given locale. Returns a list of names from the same language with
1274
         * the preferred synonym sorted first, or an empty list if none found.
1275
         * 
1276
         * @param locale
1277
         * @return Collection of ConceptNames which are synonyms for the Concept in the given locale
1278
         */
1279
        public Collection<ConceptName> getSynonyms(Locale locale) {
1280
                
1281
                List<ConceptName> syns = new ArrayList<>();
1✔
1282
                ConceptName preferredConceptName = null;
1✔
1283
                for (ConceptName possibleSynonymInLoc : getSynonyms()) {
1✔
1284
                        if (locale.equals(possibleSynonymInLoc.getLocale())) {
1✔
1285
                                if (possibleSynonymInLoc.isPreferred()) {
1✔
1286
                                        preferredConceptName = possibleSynonymInLoc;
1✔
1287
                                } else {
1288
                                        syns.add(possibleSynonymInLoc);
1✔
1289
                                }
1290
                        }
1291
                }
1✔
1292
                
1293
                // Add preferred name first in the list.
1294
                if (preferredConceptName != null) {
1✔
1295
                        syns.add(0, preferredConceptName);
1✔
1296
                }
1297
                log.debug("returning: " + syns);
1✔
1298
                return syns;
1✔
1299
        }
1300
        
1301
        /**
1302
         * Gets all the non-retired synonyms.
1303
         * 
1304
         * @return Collection of ConceptNames which are synonyms for the Concept or an empty list if
1305
         *         none is found
1306
         * @since 1.7
1307
         */
1308
        public Collection<ConceptName> getSynonyms() {
1309
                return getNames().stream()
1✔
1310
                                .filter(ConceptName::isSynonym)
1✔
1311
                                .collect(Collectors.toSet());
1✔
1312
        }
1313
        
1314
        /**
1315
         * @return Returns the version.
1316
         */
1317
        public String getVersion() {
1318
                return version;
1✔
1319
        }
1320
        
1321
        /**
1322
         * @param version The version to set.
1323
         */
1324
        public void setVersion(String version) {
1325
                this.version = version;
1✔
1326
        }
1✔
1327
        
1328
        /**
1329
         * @return Returns the conceptSets.
1330
         */
1331
        public Collection<ConceptSet> getConceptSets() {
1332
                return conceptSets;
1✔
1333
        }
1334
        
1335
        /**
1336
         * @param conceptSets The conceptSets to set.
1337
         */
1338
        public void setConceptSets(Collection<ConceptSet> conceptSets) {
1339
                this.conceptSets = conceptSets;
1✔
1340
        }
1✔
1341
        
1342
        /**
1343
         * Whether this concept is numeric or not. This will <i>always</i> return false for concept
1344
         * objects. ConceptNumeric.isNumeric() will then <i>always</i> return true.
1345
         * 
1346
         * @return false
1347
         */
1348
        public boolean isNumeric() {
1349
                return false;
1✔
1350
        }
1351
        
1352
        /**
1353
         * @return the conceptMappings for this concept
1354
         */
1355
        public Collection<ConceptMap> getConceptMappings() {
1356
                if (conceptMappings == null) {
1✔
1357
                        conceptMappings = new HashSet<>();
×
1358
                }
1359
                return conceptMappings;
1✔
1360
        }
1361
        
1362
        /**
1363
         * @param conceptMappings the conceptMappings to set
1364
         */
1365
        public void setConceptMappings(Collection<ConceptMap> conceptMappings) {
1366
                this.conceptMappings = conceptMappings;
1✔
1367
        }
1✔
1368
        
1369
        /**
1370
         * Add the given ConceptMap object to this concept's list of concept mappings. If there is
1371
         * already a corresponding ConceptMap object for this concept already, this one will not be
1372
         * added.
1373
         * 
1374
         * @param newConceptMap
1375
         */
1376
        public void addConceptMapping(ConceptMap newConceptMap) {
1377
                if (newConceptMap != null) {
1✔
1378
                        newConceptMap.setConcept(this);
1✔
1379
                }
1380
                if (newConceptMap != null && !getConceptMappings().contains(newConceptMap)) {
1✔
1381
                        if (newConceptMap.getConceptMapType() == null) {
1✔
1382
                                newConceptMap.setConceptMapType(Context.getConceptService().getDefaultConceptMapType());
1✔
1383
                        }
1384
                        getConceptMappings().add(newConceptMap);
1✔
1385
                }
1386
        }
1✔
1387
        
1388
        /**
1389
         * Child Class ConceptComplex overrides this method and returns true. See
1390
         * {@link org.openmrs.ConceptComplex#isComplex()}. Otherwise this method returns false.
1391
         * 
1392
         * @return false
1393
         * @since 1.5
1394
         */
1395
        public boolean isComplex() {
1396
                return false;
1✔
1397
        }
1398
        
1399
        /**
1400
         * Remove the given ConceptMap from the list of mappings for this Concept
1401
         * 
1402
         * @param conceptMap
1403
         * @return true if the entity was removed, false otherwise
1404
         * <strong>Should</strong> remove concept map passed from list of mappings 
1405
         */
1406
        public boolean removeConceptMapping(ConceptMap conceptMap) {
1407
                return getConceptMappings().remove(conceptMap);
1✔
1408
        }
1409
        
1410
        /**
1411
         * @see java.lang.Object#toString()
1412
         */
1413
        @Override
1414
        public String toString() {
1415
                return "Concept #" + conceptId;
1✔
1416
        }
1417
        
1418
        /**
1419
         * @see org.openmrs.Attributable#findPossibleValues(java.lang.String)
1420
         */
1421
        @Override
1422
        @Deprecated
1423
        public List<Concept> findPossibleValues(String searchText) {
1424
                List<Concept> concepts = new ArrayList<>();
1✔
1425
                try {
1426
                        
1427
                        for (ConceptSearchResult searchResult : Context.getConceptService().getConcepts(searchText,
1✔
1428
                            Collections.singletonList(Context.getLocale()), false, null, null, null, null, null, null, null)) {
1✔
1429
                                concepts.add(searchResult.getConcept());
1✔
1430
                        }
1✔
1431
                }
1432
                catch (Exception e) {
×
1433
                        // pass
1434
                }
1✔
1435
                return concepts;
1✔
1436
        }
1437
        
1438
        /**
1439
         * @see org.openmrs.Attributable#getPossibleValues()
1440
         */
1441
        @Override
1442
        @Deprecated
1443
        public List<Concept> getPossibleValues() {
1444
                try {
1445
                        return Context.getConceptService().getConceptsByName("");
×
1446
                }
1447
                catch (Exception e) {
×
1448
                        // pass
1449
                }
1450
                return Collections.emptyList();
×
1451
        }
1452
        
1453
        /**
1454
         * @see org.openmrs.Attributable#hydrate(java.lang.String)
1455
         */
1456
        @Override
1457
        public Concept hydrate(String reference) {
1458
                try {
1459
                        return Context.getConceptService().getConceptByReference(reference);
1✔
1460
                }
1461
                catch (Exception e) {
×
1462
                        // pass
1463
                }
1464
                return null;
×
1465
        }
1466
        
1467
        /**
1468
         * Turns this concept into a very simple serialized string
1469
         * 
1470
         * @see org.openmrs.Attributable#serialize()
1471
         */
1472
        @Override
1473
        public String serialize() {
1474
                if (this.getConceptId() == null) {
×
1475
                        return "";
×
1476
                }
1477
                
1478
                return "" + this.getConceptId();
×
1479
        }
1480
        
1481
        /**
1482
         * @see org.openmrs.Attributable#getDisplayString()
1483
         */
1484
        @Override
1485
        public String getDisplayString() {
1486
                if (getName() == null) {
1✔
1487
                        return toString();
×
1488
                } else {
1489
                        return getName().getName();
1✔
1490
                }
1491
        }
1492
        
1493
        /**
1494
         * Convenience method that returns a set of all the locales in which names have been added for
1495
         * this concept.
1496
         * 
1497
         * @return a set of all locales for names for this concept
1498
         * @since 1.7
1499
         * <strong>Should</strong> return all locales for conceptNames for this concept without duplicates
1500
         */
1501
        public Set<Locale> getAllConceptNameLocales() {
1502
                if (getNames().isEmpty()) {
1✔
1503
                        if (log.isDebugEnabled()) {
×
1504
                                log.debug("The Concept with id: " + conceptId + " has no names");
×
1505
                        }
1506
                        return null;
×
1507
                }
1508
                
1509
                Set<Locale> locales = new HashSet<>();
1✔
1510
                
1511
                for (ConceptName cn : getNames()) {
1✔
1512
                        locales.add(cn.getLocale());
1✔
1513
                }
1✔
1514
                
1515
                return locales;
1✔
1516
        }
1517
        
1518
        /**
1519
         * @since 1.5
1520
         * @see org.openmrs.OpenmrsObject#getId()
1521
         */
1522
        @Override
1523
        public Integer getId() {
1524
                return getConceptId();
1✔
1525
        }
1526
        
1527
        /**
1528
         * @since 1.5
1529
         * @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
1530
         */
1531
        @Override
1532
        public void setId(Integer id) {
1533
                setConceptId(id);
1✔
1534
        }
1✔
1535
        
1536
        /**
1537
         * Sort the ConceptSet based on the weight
1538
         * 
1539
         * @return sortedConceptSet Collection&lt;ConceptSet&gt;
1540
         */
1541
        private List<ConceptSet> getSortedConceptSets() {
1542
                List<ConceptSet> cs = new ArrayList<>();
1✔
1543
                if (conceptSets != null) {
1✔
1544
                        cs.addAll(conceptSets);
1✔
1545
                        Collections.sort(cs);
1✔
1546
                }
1547
                
1548
                return cs;
1✔
1549
        }
1550
        
1551
        /**
1552
         * Get all the concept members of current concept
1553
         * 
1554
         * @since 1.7
1555
         * @return List&lt;Concept&gt; the Concepts that are members of this Concept's set
1556
         * <strong>Should</strong> return concept set members sorted according to the sort weight
1557
         * <strong>Should</strong> return all the conceptMembers of current Concept
1558
         * <strong>Should</strong> return unmodifiable list of conceptMember list
1559
         * <strong>Should</strong> return concept set members sorted with retired last
1560
         */
1561
        public List<Concept> getSetMembers() {
1562
                List<Concept> conceptMembers = new ArrayList<>();
1✔
1563
                
1564
                Collection<ConceptSet> sortedConceptSet = getSortedConceptSets();
1✔
1565
                
1566
                for (ConceptSet conceptSet : sortedConceptSet) {
1✔
1567
                        conceptMembers.add(conceptSet.getConcept());
1✔
1568
                }
1✔
1569
                return Collections.unmodifiableList(conceptMembers);
1✔
1570
        }
1571

1572
        /**
1573
         * If includeRetired is true, then the returned object is the list of all the concept
1574
         * set members of current concept, else retired concept set members are excluded.
1575
         *
1576
         * @param includeRetired true/false whether to also include/exclude the retired concepts
1577
         * @since 2.5
1578
         */
1579
        public List<Concept> getSetMembers(boolean includeRetired) {
1580
                if (includeRetired) {
1✔
1581
                        return getSetMembers();
1✔
1582
                } else {
1583
                        return getSetMembers().stream()
1✔
1584
                                .filter(a -> !a.getRetired())
1✔
1585
                                .collect(Collectors.toList());
1✔
1586
                }
1587
        }
1588
        
1589
        /**
1590
         * Appends the concept to the end of the existing list of concept members for this Concept
1591
         * 
1592
         * @since 1.7
1593
         * @param setMember Concept to add to the
1594
         * <strong>Should</strong> add concept as a conceptSet
1595
         * <strong>Should</strong> append concept to the existing list of conceptSet
1596
         * <strong>Should</strong> place the new concept last in the list
1597
         * <strong>Should</strong> assign the calling component as parent to the ConceptSet
1598
         */
1599
        public void addSetMember(Concept setMember) {
1600
                addSetMember(setMember, -1);
1✔
1601
        }
1✔
1602
        
1603
        /**
1604
         * Add the concept to the existing member to the list of set members in the given location. <br>
1605
         * <br>
1606
         * index of 0 is before the first concept<br>
1607
         * index of -1 is after last.<br>
1608
         * index of 1 is after the first but before the second, etc<br>
1609
         * 
1610
         * @param setMember the Concept to add as a child of this Concept
1611
         * @param index where in the list of set members to put this setMember
1612
         * @since 1.7
1613
         * <strong>Should</strong> assign the given concept as a ConceptSet
1614
         * <strong>Should</strong> insert the concept before the first with zero index
1615
         * <strong>Should</strong> insert the concept at the end with negative one index
1616
         * <strong>Should</strong> insert the concept in the third slot
1617
         * <strong>Should</strong> assign the calling component as parent to the ConceptSet
1618
         * <strong>Should</strong> add the concept to the current list of conceptSet
1619
         * @see #getSortedConceptSets()
1620
         */
1621
        public void addSetMember(Concept setMember, int index) {
1622
                List<ConceptSet> sortedConceptSets = getSortedConceptSets();
1✔
1623
                int setsSize = sortedConceptSets.size();
1✔
1624
                
1625
                //after sorting, we need to reset the sort weights because retired
1626
                //sets have moved to the bottom and hence need to be reassigned
1627
                //higher sort weights than the non retired ones
1628
                double weight = 990.0;
1✔
1629
                for (ConceptSet conceptSet : sortedConceptSets) {
1✔
1630
                        weight += 10.0;
1✔
1631
                        conceptSet.setSortWeight(weight);
1✔
1632
                }
1✔
1633
                
1634
                if (sortedConceptSets.isEmpty()) {
1✔
1635
                        weight = 1000.0;
1✔
1636
                } else if (index == -1 || index >= setsSize) {
1✔
1637
                        // deals with list size of 1 and any large index given by dev
1638
                        weight = sortedConceptSets.get(setsSize - 1).getSortWeight() + 10.0;
1✔
1639
                } else if (index == 0) {
1✔
1640
                        weight = sortedConceptSets.get(0).getSortWeight() - 10.0;
1✔
1641
                } else {
1642
                        // put the weight between two
1643
                        double prevSortWeight = sortedConceptSets.get(index - 1).getSortWeight();
1✔
1644
                        double nextSortWeight = sortedConceptSets.get(index).getSortWeight();
1✔
1645
                        weight = (prevSortWeight + nextSortWeight) / 2;
1✔
1646
                }
1647
                
1648
                ConceptSet conceptSet = new ConceptSet(setMember, weight);
1✔
1649
                conceptSet.setConceptSet(this);
1✔
1650
                conceptSets.add(conceptSet);
1✔
1651
        }
1✔
1652

1653
        /**
1654
         * @see org.openmrs.customdatatype.Customizable#getAttributes()
1655
         */
1656
        @Override
1657
        public Set<ConceptAttribute> getAttributes() {
1658
                if (attributes == null) {
1✔
1659
                        attributes = new LinkedHashSet<>();
×
1660
                }
1661
                return attributes;
1✔
1662
        }
1663

1664
        /**
1665
         * @see org.openmrs.customdatatype.Customizable#getActiveAttributes()
1666
         */
1667
        @Override
1668
        public Collection<ConceptAttribute> getActiveAttributes() {
1669
                return getAttributes().stream()
1✔
1670
                                .filter(attr -> !attr.getVoided())
1✔
1671
                                .collect(Collectors.toList());
1✔
1672
        }
1673

1674
        /**
1675
         * @see org.openmrs.customdatatype.Customizable#getActiveAttributes(org.openmrs.customdatatype.CustomValueDescriptor)
1676
         */
1677
        @Override
1678
        public List<ConceptAttribute> getActiveAttributes(CustomValueDescriptor ofType) {
1679
                return getAttributes().stream()
×
1680
                                .filter(attr -> attr.getAttributeType().equals(ofType) && !attr.getVoided())
×
1681
                                .collect(Collectors.toList());
×
1682
        }
1683

1684
        /**
1685
         * @param attributes the attributes to set
1686
         */
1687
        public void setAttributes(Set<ConceptAttribute> attributes) {
1688
                this.attributes = attributes;
1✔
1689
        }
1✔
1690

1691
        /**
1692
         * @see org.openmrs.customdatatype.Customizable#addAttribute(Attribute)
1693
         */
1694
        @Override
1695
        public void addAttribute(ConceptAttribute attribute) {
1696
                getAttributes().add(attribute);
×
1697
                attribute.setOwner(this);
×
1698
        }
×
1699

1700
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc