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

openmrs / openmrs-core / 10290281517

07 Aug 2024 07:08PM CUT coverage: 64.834% (-0.1%) from 64.952%
10290281517

push

github

web-flow
maven(deps): bump org.apache.commons:commons-lang3 from 3.15.0 to 3.16.0 (#4707)

Bumps org.apache.commons:commons-lang3 from 3.15.0 to 3.16.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

22918 of 35349 relevant lines covered (64.83%)

0.65 hits per line

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

83.13
/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.annotations.ContainedIn;
31
import org.hibernate.search.annotations.DocumentId;
32
import org.hibernate.search.annotations.Field;
33
import org.hibernate.search.annotations.FullTextFilterDef;
34
import org.hibernate.search.annotations.FullTextFilterDefs;
35
import org.hibernate.search.annotations.IndexedEmbedded;
36
import org.openmrs.annotation.AllowDirectAccess;
37
import org.openmrs.api.APIException;
38
import org.openmrs.api.ConceptNameType;
39
import org.openmrs.api.ConceptService;
40
import org.openmrs.api.context.Context;
41
import org.openmrs.api.db.hibernate.search.TermsFilterFactory;
42
import org.openmrs.customdatatype.CustomValueDescriptor;
43
import org.openmrs.customdatatype.Customizable;
44
import org.openmrs.util.LocaleUtility;
45
import org.openmrs.util.OpenmrsUtil;
46
import org.slf4j.Logger;
47
import org.slf4j.LoggerFactory;
48
import org.springframework.util.ObjectUtils;
49

50
/**
51
 * A Concept object can represent either a question or an answer to a data point. That data point is
52
 * usually an {@link Obs}. <br>
53
 * <br>
54
 * A Concept can have multiple names and multiple descriptions within one locale and across multiple
55
 * locales.<br>
56
 * <br>
57
 * To save a Concept to the database, first build up the Concept object in java, then pass that
58
 * object to the {@link ConceptService}.<br>
59
 * <br>
60
 * To get a Concept that is stored in the database, call a method in the {@link ConceptService} to
61
 * fetch an object. To get child objects off of that Concept, further calls to the
62
 * {@link ConceptService} or the database are not needed. e.g. To get the list of answers that are
63
 * stored to a concept, get the concept, then call {@link Concept#getAnswers()}
64
 * 
65
 * @see ConceptName
66
 * @see ConceptDescription
67
 * @see ConceptAnswer
68
 * @see ConceptSet
69
 * @see ConceptMap
70
 * @see ConceptService
71
 */
72
@FullTextFilterDefs( { @FullTextFilterDef(name = "termsFilterFactory", impl = TermsFilterFactory.class) })
73
@Audited
74
public class Concept extends BaseOpenmrsObject implements Auditable, Retireable, Serializable, Attributable<Concept>,Customizable<ConceptAttribute> {
75
        
76
        public static final long serialVersionUID = 57332L;
77
        
78
        private static final Logger log = LoggerFactory.getLogger(Concept.class);
1✔
79
        private static final String CONCEPT_NAME_LOCALE_NULL = "Concept.name.locale.null";
80
        
81
        // Fields
82
        @DocumentId
83
        private Integer conceptId;
84
        
85
        @Field
1✔
86
        private Boolean retired = false;
1✔
87
        
88
        private User retiredBy;
89
        
90
        private Date dateRetired;
91
        
92
        private String retireReason;
93
        
94
        @IndexedEmbedded(includeEmbeddedObjectId = true)
95
        private ConceptDatatype datatype;
96
        
97
        @IndexedEmbedded(includeEmbeddedObjectId = true)
98
        private ConceptClass conceptClass;
99
        
100
        private Boolean set = false;
1✔
101
        
102
        private String version;
103
        
104
        private User creator;
105
        
106
        private Date dateCreated;
107
        
108
        private User changedBy;
109
        
110
        private Date dateChanged;
111
        
112
        @AllowDirectAccess
113
        @ContainedIn
114
        private Collection<ConceptName> names;
115
        
116
        @AllowDirectAccess
117
        private Collection<ConceptAnswer> answers;
118
        
119
        private Collection<ConceptSet> conceptSets;
120
        
121
        private Collection<ConceptDescription> descriptions;
122
        
123
        @IndexedEmbedded(includeEmbeddedObjectId = true)
124
        private Collection<ConceptMap> conceptMappings;
125
        
126
        /**
127
         * A cache of locales to names which have compatible locales. Built on-the-fly by
128
         * getCompatibleNames().
129
         */
130
        private Map<Locale, List<ConceptName>> compatibleCache;
131

132
        private Set<ConceptAttribute> attributes = new LinkedHashSet<>();
1✔
133

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

188
        /**
189
         * Set this Concept as having the given <code>answers</code>; This method assumes that the
190
         * sort_weight has already been set.
191
         * 
192
         * @param answers The answers to set.
193
         */
194
        public void setAnswers(Collection<ConceptAnswer> answers) {
195
                this.answers = answers;
1✔
196
        }
1✔
197
        
198
        /**
199
         * Add the given ConceptAnswer to the list of answers for this Concept
200
         * 
201
         * @param conceptAnswer
202
         * <strong>Should</strong> add the ConceptAnswer to Concept
203
         * <strong>Should</strong> not fail if answers list is null
204
         * <strong>Should</strong> not fail if answers contains ConceptAnswer already
205
         * <strong>Should</strong> set the sort weight to the max plus one if not provided
206
         */
207
        public void addAnswer(ConceptAnswer conceptAnswer) {
208
                if (conceptAnswer != null) {
1✔
209
                        if (!getAnswers().contains(conceptAnswer)) {
1✔
210
                                conceptAnswer.setConcept(this);
1✔
211
                                getAnswers().add(conceptAnswer);
1✔
212
                        }
213
                        
214
                        if ((conceptAnswer.getSortWeight() == null) || (conceptAnswer.getSortWeight() <= 0)) {
1✔
215
                                //find largest sort weight
216
                                ConceptAnswer a = Collections.max(answers);
1✔
217
                                //a.sortWeight can be NULL
218
                                Double sortWeight = (a == null) ? 1d : ((a.getSortWeight() == null) ? 1d : a.getSortWeight() + 1d);
1✔
219
                                conceptAnswer.setSortWeight(sortWeight);
1✔
220
                        }
221
                }
222
        }
1✔
223
        
224
        /**
225
         * Remove the given answer from the list of answers for this Concept
226
         * 
227
         * @param conceptAnswer answer to remove
228
         * @return true if the entity was removed, false otherwise
229
         * <strong>Should</strong> not fail if answers is empty
230
         * <strong>Should</strong> not fail if given answer does not exist in list
231
         */
232
        public boolean removeAnswer(ConceptAnswer conceptAnswer) {
233
                return getAnswers().remove(conceptAnswer);
1✔
234
        }
235
        
236
        /**
237
         * @return Returns the changedBy.
238
         */
239
        @Override
240
        public User getChangedBy() {
241
                return changedBy;
1✔
242
        }
243
        
244
        /**
245
         * @param changedBy The changedBy to set.
246
         */
247
        @Override
248
        public void setChangedBy(User changedBy) {
249
                this.changedBy = changedBy;
1✔
250
        }
1✔
251
        
252
        /**
253
         * @return Returns the conceptClass.
254
         */
255
        public ConceptClass getConceptClass() {
256
                return conceptClass;
1✔
257
        }
258
        
259
        /**
260
         * @param conceptClass The conceptClass to set.
261
         */
262
        public void setConceptClass(ConceptClass conceptClass) {
263
                this.conceptClass = conceptClass;
1✔
264
        }
1✔
265
        
266
        /**
267
         * whether or not this concept is a set
268
         * 
269
         * @deprecated as of 2.0, use {@link #getSet()}
270
         */
271
        @Deprecated
272
        @JsonIgnore
273
        public Boolean isSet() {
274
                return getSet();
×
275
        }
276
        
277
        /**
278
         * @param set whether or not this concept is a set
279
         */
280
        public void setSet(Boolean set) {
281
                this.set = set;
1✔
282
        }
1✔
283
        
284
        public Boolean getSet() {
285
                return set;
1✔
286
        }
287
        
288
        /**
289
         * @return Returns the conceptDatatype.
290
         */
291
        public ConceptDatatype getDatatype() {
292
                return datatype;
1✔
293
        }
294
        
295
        /**
296
         * @param conceptDatatype The conceptDatatype to set.
297
         */
298
        public void setDatatype(ConceptDatatype conceptDatatype) {
299
                this.datatype = conceptDatatype;
1✔
300
        }
1✔
301
        
302
        /**
303
         * @return Returns the conceptId.
304
         */
305
        public Integer getConceptId() {
306
                return conceptId;
1✔
307
        }
308
        
309
        /**
310
         * @param conceptId The conceptId to set.
311
         */
312
        public void setConceptId(Integer conceptId) {
313
                this.conceptId = conceptId;
1✔
314
        }
1✔
315
        
316
        /**
317
         * @return Returns the creator.
318
         */
319
        @Override
320
        public User getCreator() {
321
                return creator;
1✔
322
        }
323
        
324
        /**
325
         * @param creator The creator to set.
326
         */
327
        @Override
328
        public void setCreator(User creator) {
329
                this.creator = creator;
1✔
330
        }
1✔
331
        
332
        /**
333
         * @return Returns the dateChanged.
334
         */
335
        @Override
336
        public Date getDateChanged() {
337
                return dateChanged;
1✔
338
        }
339
        
340
        /**
341
         * @param dateChanged The dateChanged to set.
342
         */
343
        @Override
344
        public void setDateChanged(Date dateChanged) {
345
                this.dateChanged = dateChanged;
1✔
346
        }
1✔
347
        
348
        /**
349
         * @return Returns the dateCreated.
350
         */
351
        @Override
352
        public Date getDateCreated() {
353
                return dateCreated;
1✔
354
        }
355
        
356
        /**
357
         * @param dateCreated The dateCreated to set.
358
         */
359
        @Override
360
        public void setDateCreated(Date dateCreated) {
361
                this.dateCreated = dateCreated;
1✔
362
        }
1✔
363
        
364
        /**
365
         * Sets the preferred name /in this locale/ to the specified conceptName and its Locale, if
366
         * there is an existing preferred name for this concept in the same locale, this one will
367
         * replace the old preferred name. Also, the name is added to the concept if it is not already
368
         * among the concept names.
369
         * 
370
         * @param preferredName The name to be marked as preferred in its locale
371
         * <strong>Should</strong> only allow one preferred name
372
         * <strong>Should</strong> add the name to the list of names if it not among them before
373
         * <strong>Should</strong> fail if the preferred name to set to is an index term
374
         */
375
        public void setPreferredName(ConceptName preferredName) {
376
                
377
                if (preferredName == null || preferredName.getVoided() || preferredName.isIndexTerm()) {
1✔
378
                        throw new APIException("Concept.error.preferredName.null", (Object[]) null);
1✔
379
                } else if (preferredName.getLocale() == null) {
1✔
380
                        throw new APIException(CONCEPT_NAME_LOCALE_NULL, (Object[]) null);
×
381
                }
382
                
383
                //first revert the current preferred name(if any) from being preferred
384
                ConceptName oldPreferredName = getPreferredName(preferredName.getLocale());
1✔
385
                if (oldPreferredName != null) {
1✔
386
                        oldPreferredName.setLocalePreferred(false);
1✔
387
                }
388
                
389
                preferredName.setLocalePreferred(true);
1✔
390
                //add this name, if it is new or not among this concept's names
391
                if (preferredName.getConceptNameId() == null || !getNames().contains(preferredName)) {
1✔
392
                        addName(preferredName);
1✔
393
                }
394
        }
1✔
395
        
396
        /**
397
         * A convenience method to get the concept-name (if any) which has a particular tag. This does
398
         * not guarantee that the returned name is the only one with the tag.
399
         * 
400
         * @param conceptNameTag the tag for which to look
401
         * @return the tagged name, or null if no name has the tag
402
         */
403
        public ConceptName findNameTaggedWith(ConceptNameTag conceptNameTag) {
404
                ConceptName taggedName = null;
×
405
                for (ConceptName possibleName : getNames()) {
×
406
                        if (possibleName.hasTag(conceptNameTag)) {
×
407
                                taggedName = possibleName;
×
408
                                break;
×
409
                        }
410
                }
×
411
                return taggedName;
×
412
        }
413
        
414
        /**
415
         * Returns a name in the given locale. If a name isn't found with an exact match, a compatible
416
         * locale match is returned. If no name is found matching either of those, the first name
417
         * defined for this concept is returned.
418
         * 
419
         * @param locale the locale to fetch for
420
         * @return ConceptName attributed to the Concept in the given locale
421
         * @since 1.5
422
         * @see Concept#getNames(Locale) to get all the names for a locale,
423
         * @see Concept#getPreferredName(Locale) for the preferred name (if any)
424
         */
425
        public ConceptName getName(Locale locale) {
426
                return getName(locale, false);
1✔
427
        }
428
        
429
        /**
430
         * Returns concept name, the look up for the appropriate name is done in the following order;
431
         * <ul>
432
         * <li>First name found in any locale that is explicitly marked as preferred while searching
433
         * available locales in order of preference (the locales are traversed in their order as they
434
         * are listed in the 'locale.allowed.list' including english global property).</li>
435
         * <li>First "Fully Specified" name found while searching available locales in order of
436
         * preference.</li>
437
         * <li>The first fully specified name found while searching through all names for the concept</li>
438
         * <li>The first synonym found while searching through all names for the concept.</li>
439
         * <li>The first random name found(except index terms) while searching through all names.</li>
440
         * </ul>
441
         * 
442
         * @return {@link ConceptName} in the current locale or any locale if none found
443
         * @since 1.5
444
         * @see Concept#getNames(Locale) to get all the names for a locale
445
         * @see Concept#getPreferredName(Locale) for the preferred name (if any)
446
         * <strong>Should</strong> return the name explicitly marked as locale preferred if any is present
447
         * <strong>Should</strong> return the fully specified name in a locale if no preferred name is set
448
         * <strong>Should</strong> return null if the only added name is an index term
449
         * <strong>Should</strong> return name in broader locale in case none is found in specific one
450
         */
451
        public ConceptName getName() {
452
                if (getNames().isEmpty()) {
1✔
453
                        log.debug("there are no names defined for: {}", conceptId);
1✔
454
                        return null;
1✔
455
                }
456
                
457
                for (Locale currentLocale : LocaleUtility.getLocalesInOrder()) {
1✔
458
                        ConceptName preferredName = getPreferredName(currentLocale);
1✔
459
                        if (preferredName != null) {
1✔
460
                                return preferredName;
1✔
461
                        }
462
                        
463
                        ConceptName fullySpecifiedName = getFullySpecifiedName(currentLocale);
1✔
464
                        if (fullySpecifiedName != null) {
1✔
465
                                return fullySpecifiedName;
×
466
                        }
467
                        
468
                        //if the locale has an variants e.g en_GB, try names in the locale excluding the country code i.e en
469
                        if (!StringUtils.isBlank(currentLocale.getCountry()) || !StringUtils.isBlank(currentLocale.getVariant())) {
1✔
470
                                Locale broaderLocale = new Locale(currentLocale.getLanguage());
1✔
471
                                ConceptName prefNameInBroaderLoc = getPreferredName(broaderLocale);
1✔
472
                                if (prefNameInBroaderLoc != null) {
1✔
473
                                        return prefNameInBroaderLoc;
1✔
474
                                }
475
                                
476
                                ConceptName fullySpecNameInBroaderLoc = getFullySpecifiedName(broaderLocale);
1✔
477
                                if (fullySpecNameInBroaderLoc != null) {
1✔
478
                                        return fullySpecNameInBroaderLoc;
×
479
                                }
480
                        }
481
                }
1✔
482
                
483
                for (ConceptName cn : getNames()) {
1✔
484
                        if (cn.isFullySpecifiedName()) {
1✔
485
                                return cn;
×
486
                        }
487
                }
1✔
488
                
489
                if (!getSynonyms().isEmpty()) {
1✔
490
                        return getSynonyms().iterator().next();
×
491
                }
492
                
493
                // we don't expect to get here since every concept name must have at least
494
                // one fully specified name, but just in case (probably inconsistent data)
495
                
496
                return null;
1✔
497
        }
498
        
499
        /**
500
         * Checks whether this concept has the given string in any of the names in the given locale
501
         * already.
502
         * 
503
         * @param name the ConceptName.name to compare to
504
         * @param locale the locale to look in (null to check all locales)
505
         * @return true/false whether the name exists already
506
         * <strong>Should</strong> return false if name is null
507
         * <strong>Should</strong> return true if locale is null but name exists
508
         * <strong>Should</strong> return false if locale is null but name does not exist
509
         */
510
        public boolean hasName(String name, Locale locale) {
511
                if (name == null) {
1✔
512
                        return false;
1✔
513
                }
514
                
515
                Collection<ConceptName> currentNames;
516
                if (locale == null) {
1✔
517
                        currentNames = getNames();
1✔
518
                } else {
519
                        currentNames = getNames(locale);
1✔
520
                }
521
                
522
                for (ConceptName currentName : currentNames) {
1✔
523
                        if (name.equalsIgnoreCase(currentName.getName())) {
1✔
524
                                return true;
1✔
525
                        }
526
                }
1✔
527
                
528
                return false;
1✔
529
        }
530
        
531
        /**
532
         * Returns concept name depending of locale, type (short, fully specified, etc) and tag.
533
         * Searches in the locale, and then the locale's parent if nothing is found.
534
         * 
535
         * @param ofType find a name of this type (optional)
536
         * @param havingTag find a name with this tag (optional)
537
         * @param locale find a name with this locale (required)
538
         * @return a name that matches the arguments, or null if none is found. If there are multiple
539
         *         matches and one is locale_preferred, that will be returned, otherwise a random one of
540
         *         the matches will be returned.
541
         * @since 1.9
542
         **/
543
        public ConceptName getName(Locale locale, ConceptNameType ofType, ConceptNameTag havingTag) {
544
                Collection<ConceptName> namesInLocale = getNames(locale);
×
545
                if (!namesInLocale.isEmpty()) {
×
546
                        //Pass the possible candidates through a stream and save the ones that match requirements to the list
547
                        List<ConceptName> matches = namesInLocale.stream().filter(
×
548
                                c->(ofType==null || ofType.equals(c.getConceptNameType())) && (havingTag==null || c.hasTag(havingTag))
×
549
                        ).collect(Collectors.toList());
×
550
                        
551
                        // if we have any matches, we'll return one of them
552
                        if (matches.size() == 1) {
×
553
                                return matches.get(0);
×
554
                        } else if (matches.size() > 1) {
×
555
                                for (ConceptName match : matches) {
×
556
                                        if (match.getLocalePreferred()) {
×
557
                                                return match;
×
558
                                        }
559
                                }
×
560
                                // none was explicitly marked as preferred
561
                                return matches.get(0);
×
562
                        }
563
                }
564
                
565
                // if we reach here, there were no matching names, so try to look in the parent locale
566
                Locale parent = new Locale(locale.getLanguage());
×
567
                if (!parent.equals(locale)) {
×
568
                        return getName(parent, ofType, havingTag);
×
569
                } else {
570
                        return null;
×
571
                }
572
        }
573
        
574
        /**
575
         * Returns a name in the given locale. If a name isn't found with an exact match, a compatible
576
         * locale match is returned. If no name is found matching either of those, the first name
577
         * defined for this concept is returned.
578
         * 
579
         * @param locale the language and country in which the name is used
580
         * @param exact true/false to return only exact locale (no default locale)
581
         * @return the closest name in the given locale, or the first name
582
         * @see Concept#getNames(Locale) to get all the names for a locale,
583
         * @see Concept#getPreferredName(Locale) for the preferred name (if any)
584
         * <strong>Should</strong> return exact name locale match given exact equals true
585
         * <strong>Should</strong> return loose match given exact equals false
586
         * <strong>Should</strong> return null if no names are found in locale given exact equals true
587
         * <strong>Should</strong> return any name if no locale match given exact equals false
588
         * <strong>Should</strong> return name in broader locale in case none is found in specific one
589
         */
590
        public ConceptName getName(Locale locale, boolean exact) {
591
                
592
                // fail early if this concept has no names defined
593
                if (getNames().isEmpty()) {
1✔
594
                        log.debug("there are no names defined for: {}", conceptId);
1✔
595
                        return null;
1✔
596
                }
597
                
598
                log.debug("Getting conceptName for locale: {}", locale);
1✔
599
                
600
                ConceptName exactName = getNameInLocale(locale);
1✔
601
                
602
                if (exactName != null) {
1✔
603
                        return exactName;
1✔
604
                }
605
                
606
                if (!exact) {
1✔
607
                        Locale broaderLocale = new Locale(locale.getLanguage());
1✔
608
                        ConceptName name = getNameInLocale(broaderLocale);
1✔
609
                        return name != null ? name : getName();
1✔
610
                }
611
                return null;
1✔
612
        }
613
        
614
        /**
615
         * Gets the best name in the specified locale.
616
         * 
617
         * @param locale
618
         * @return null if name in given locale doesn't exist
619
         */
620
        private ConceptName getNameInLocale(Locale locale) {
621
                ConceptName preferredName = getPreferredName(locale);
1✔
622
                if (preferredName != null) {
1✔
623
                        return preferredName;
1✔
624
                }
625
                
626
                ConceptName fullySpecifiedName = getFullySpecifiedName(locale);
1✔
627
                if (fullySpecifiedName != null) {
1✔
628
                        return fullySpecifiedName;
×
629
                } else if (!getSynonyms(locale).isEmpty()) {
1✔
630
                        return getSynonyms(locale).iterator().next();
1✔
631
                }
632
                
633
                return null;
1✔
634
        }
635
        
636
        /**
637
         * Returns the name which is explicitly marked as preferred for a given locale.
638
         * 
639
         * @param forLocale locale for which to return a preferred name
640
         * @return preferred name for the locale, or null if no preferred name is specified
641
         * <strong>Should</strong> return the concept name explicitly marked as locale preferred
642
         * <strong>Should</strong> return the fully specified name if no name is explicitly marked as locale preferred
643
         */
644
        public ConceptName getPreferredName(Locale forLocale) {
645
                
646
                if (log.isDebugEnabled()) {
1✔
647
                        log.debug("Getting preferred conceptName for locale: " + forLocale);
×
648
                }
649
                // fail early if this concept has no names defined
650
                if (getNames(forLocale).isEmpty()) {
1✔
651
                        log.debug("there are no names defined for concept with id: {} in the locale: {}", conceptId, forLocale);
1✔
652
                        return null;
1✔
653
                } else if (forLocale == null) {
1✔
654
                        log.warn("Locale cannot be null");
×
655
                        return null;
×
656
                }
657
                
658
                for (ConceptName nameInLocale : getNames(forLocale)) {
1✔
659
                        if (ObjectUtils.nullSafeEquals(nameInLocale.getLocalePreferred(), true)) {
1✔
660
                                return nameInLocale;
1✔
661
                        }
662
                }
1✔
663
                
664
                // look for partially locale match - any language matches takes precedence over country matches.
665
                ConceptName bestMatch = null;
1✔
666
                
667
                for (ConceptName nameInLocale : getPartiallyCompatibleNames(forLocale)) {
1✔
668
                        if (ObjectUtils.nullSafeEquals(nameInLocale.getLocalePreferred(), true)) {
1✔
669
                                Locale nameLocale = nameInLocale.getLocale();
1✔
670
                                if (forLocale.getLanguage().equals(nameLocale.getLanguage())) {
1✔
671
                                        return nameInLocale;
1✔
672
                                } else {
673
                                        bestMatch = nameInLocale;
×
674
                                }
675
                                
676
                        }
677
                }
1✔
678
                
679
                if (bestMatch != null) {
1✔
680
                        return bestMatch;
×
681
                }
682
                
683
                return getFullySpecifiedName(forLocale);
1✔
684
        }
685
        
686
        /**
687
         * Convenience method that returns the fully specified name in the locale
688
         * 
689
         * @param locale locale from which to look up the fully specified name
690
         * @return the name explicitly marked as fully specified for the locale
691
         * <strong>Should</strong> return the name marked as fully specified for the given locale
692
         */
693
        public ConceptName getFullySpecifiedName(Locale locale) {
694
                if (locale != null && !getNames(locale).isEmpty()) {
1✔
695
                        //get the first fully specified name, since every concept must have a fully specified name,
696
                        //then, this loop will have to return a name
697
                        for (ConceptName conceptName : getNames(locale)) {
1✔
698
                                if (ObjectUtils.nullSafeEquals(conceptName.isFullySpecifiedName(), true)) {
1✔
699
                                        return conceptName;
1✔
700
                                }
701
                        }
1✔
702
                        
703
                        // look for partially locale match - any language matches takes precedence over country matches.
704
                        ConceptName bestMatch = null;
1✔
705
                        for (ConceptName conceptName : getPartiallyCompatibleNames(locale)) {
1✔
706
                                if (ObjectUtils.nullSafeEquals(conceptName.isFullySpecifiedName(), true)) {
1✔
707
                                        Locale nameLocale = conceptName.getLocale();
1✔
708
                                        if (locale.getLanguage().equals(nameLocale.getLanguage())) {
1✔
709
                                                return conceptName;
1✔
710
                                        }
711
                                        bestMatch = conceptName;
×
712
                                }
713
                        }
1✔
714
                        return bestMatch;
1✔
715
                        
716
                }
717
                return null;
1✔
718
        }
719
        
720
        /**
721
         * Returns all names available in a specific locale. <br>
722
         * <br>
723
         * This is recommended when managing the concept dictionary.
724
         * 
725
         * @param locale locale for which names should be returned
726
         * @return Collection of ConceptNames with the given locale
727
         */
728
        public Collection<ConceptName> getNames(Locale locale) {
729
                return getNames().stream()
1✔
730
                                .filter(n -> n.getLocale().equals(locale))
1✔
731
                                .collect(Collectors.toSet());
1✔
732
        }
733
        
734
        /**
735
         * Returns all names available for locale language "or" country. <br>
736
         * <br>
737
         * 
738
         * @param locale locale for which names should be returned
739
         * @return Collection of ConceptNames with the given locale language or country
740
         */
741
        private Collection<ConceptName> getPartiallyCompatibleNames(Locale locale) {
742
                String language = locale.getLanguage();
1✔
743
                String country = locale.getCountry();
1✔
744
                
745
                return getNames().stream()
1✔
746
                                .filter(n -> language.equals(n.getLocale().getLanguage()) || 
1✔
747
                                                        StringUtils.isNotBlank(country) && country.equals(n.getLocale().getCountry()))
1✔
748
                                .collect(Collectors.toSet());
1✔
749
        }
750
        
751
        /**
752
         * Returns all names from compatible locales. A locale is considered compatible if it is exactly
753
         * the same locale, or if either locale has no country specified and the language matches. <br>
754
         * <br>
755
         * This is recommended when presenting possible names to the use.
756
         * 
757
         * @param desiredLocale locale with which the names should be compatible
758
         * @return Collection of compatible names
759
         * <strong>Should</strong> exclude incompatible country locales
760
         * <strong>Should</strong> exclude incompatible language locales
761
         */
762
        public List<ConceptName> getCompatibleNames(Locale desiredLocale) {
763
                // lazy create the cache
764
                List<ConceptName> compatibleNames = null;
1✔
765
                if (compatibleCache == null) {
1✔
766
                        compatibleCache = new HashMap<>();
1✔
767
                } else {
768
                        compatibleNames = compatibleCache.get(desiredLocale);
×
769
                }
770
                
771
                if (compatibleNames == null) {
1✔
772
                        compatibleNames = new ArrayList<>();
1✔
773
                        for (ConceptName possibleName : getNames()) {
1✔
774
                                if (LocaleUtility.areCompatible(possibleName.getLocale(), desiredLocale)) {
1✔
775
                                        compatibleNames.add(possibleName);
1✔
776
                                }
777
                        }
1✔
778
                        compatibleCache.put(desiredLocale, compatibleNames);
1✔
779
                }
780
                return compatibleNames;
1✔
781
        }
782
        
783
        /**
784
         * Sets the specified name as the fully specified name for the locale and the current fully
785
         * specified (if any) ceases to be the fully specified name for the locale.
786
         * 
787
         * @param fullySpecifiedName the new fully specified name to set
788
         * <strong>Should</strong> set the concept name type of the specified name to fully specified
789
         * <strong>Should</strong> convert the previous fully specified name if any to a synonym
790
         * <strong>Should</strong> add the name to the list of names if it not among them before
791
         */
792
        public void setFullySpecifiedName(ConceptName fullySpecifiedName) {
793
                if (fullySpecifiedName == null || fullySpecifiedName.getLocale() == null) {
1✔
794
                        throw new APIException(CONCEPT_NAME_LOCALE_NULL, (Object[]) null);
×
795
                } else if (fullySpecifiedName.getVoided()) {
1✔
796
                        throw new APIException("Concept.error.fullySpecifiedName.null", (Object[]) null);
×
797
                }
798
                
799
                ConceptName oldFullySpecifiedName = getFullySpecifiedName(fullySpecifiedName.getLocale());
1✔
800
                if (oldFullySpecifiedName != null) {
1✔
801
                        oldFullySpecifiedName.setConceptNameType(null);
1✔
802
                }
803
                fullySpecifiedName.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
1✔
804
                //add this name, if it is new or not among this concept's names
805
                if (fullySpecifiedName.getConceptNameId() == null || !getNames().contains(fullySpecifiedName)) {
1✔
806
                        addName(fullySpecifiedName);
1✔
807
                }
808
        }
1✔
809
        
810
        /**
811
         * Sets the specified name as the short name for the locale and the current shortName(if any)
812
         * ceases to be the short name for the locale.
813
         * 
814
         * @param shortName the new shortName to set
815
         * <strong>Should</strong> set the concept name type of the specified name to short
816
         * <strong>Should</strong> convert the previous shortName if any to a synonym
817
         * <strong>Should</strong> add the name to the list of names if it not among them before
818
         * <strong>Should</strong> void old short name if new one is blank (do not save blanks!)
819
         */
820
        public void setShortName(ConceptName shortName) {
821
                if (shortName != null) {
1✔
822
                        if (shortName.getLocale() == null) {
1✔
823
                                throw new APIException(CONCEPT_NAME_LOCALE_NULL, (Object[]) null);
×
824
                        }
825
                        ConceptName oldShortName = getShortNameInLocale(shortName.getLocale());
1✔
826
                        if (oldShortName != null) {
1✔
827
                                oldShortName.setConceptNameType(null);
1✔
828
                        }
829
                        shortName.setConceptNameType(ConceptNameType.SHORT);
1✔
830
                        if (StringUtils.isNotBlank(shortName.getName())
1✔
831
                                && (shortName.getConceptNameId() == null || !getNames().contains(shortName))) {
1✔
832
                                //add this name, if it is new or not among this concept's names
833
                                addName(shortName);
1✔
834
                        }
835
                } else {
1✔
836
                        throw new APIException("Concept.error.shortName.null", (Object[]) null);
×
837
                }
838
        }
1✔
839
        
840
        /**
841
         * Gets the explicitly specified short name for a locale.
842
         * 
843
         * @param locale locale for which to find a short name
844
         * @return the short name, or null if none has been explicitly set
845
         */
846
        public ConceptName getShortNameInLocale(Locale locale) {
847
                ConceptName bestMatch = null;
1✔
848
                if (locale != null && !getShortNames().isEmpty()) {
1✔
849
                        for (ConceptName shortName : getShortNames()) {
1✔
850
                                Locale nameLocale = shortName.getLocale();
1✔
851
                                if (nameLocale.equals(locale)) {
1✔
852
                                        return shortName;
1✔
853
                                }
854
                                // test for partially locale match - any language matches takes precedence over country matches.
855
                                if (OpenmrsUtil.nullSafeEquals(locale.getLanguage(), nameLocale.getLanguage())) {
1✔
856
                                        bestMatch = shortName;
1✔
857
                                } else if (bestMatch == null && StringUtils.isNotBlank(locale.getCountry())
1✔
858
                                        && locale.getCountry().equals(nameLocale.getCountry())) {
×
859
                                        bestMatch = shortName;
×
860
                                }
861
                        }
1✔
862
                }
863
                return bestMatch;
1✔
864
        }
865
        
866
        /**
867
         * Gets a collection of short names for this concept from all locales.
868
         * 
869
         * @return a collection of all short names for this concept
870
         */
871
        public Collection<ConceptName> getShortNames() {
872
                List<ConceptName> shortNames = new ArrayList<>();
1✔
873
                if (getNames().isEmpty()) {
1✔
874
                        if (log.isDebugEnabled()) {
×
875
                                log.debug("The Concept with id: " + conceptId + " has no names");
×
876
                        }
877
                } else {
878
                        shortNames = getNames().stream()
1✔
879
                                                        .filter(ConceptName::isShort)
1✔
880
                                                        .collect(Collectors.toList());
1✔
881
                }
882
                return shortNames;
1✔
883
        }
884
        
885
        /**
886
         * Returns the short form name for a locale, or if none has been identified, the shortest name
887
         * available in the locale. If exact is false, the shortest name from any locale is returned
888
         * 
889
         * @param locale the language and country in which the short name is used
890
         * @param exact true/false to return only exact locale (no default locale)
891
         * @return the appropriate short name, or null if not found
892
         * <strong>Should</strong> return the name marked as the shortName for the locale if it is present
893
         * <strong>Should</strong> return the shortest name in a given locale for a concept if exact is true
894
         * <strong>Should</strong> return the shortest name for the concept from any locale if exact is false
895
         * <strong>Should</strong> return null if there are no names in the specified locale and exact is true
896
         */
897
        public ConceptName getShortestName(Locale locale, Boolean exact) {
898
                if (log.isDebugEnabled()) {
1✔
899
                        log.debug("Getting shortest conceptName for locale: " + locale);
×
900
                }
901
                
902
                ConceptName shortNameInLocale = getShortNameInLocale(locale);
1✔
903
                if (shortNameInLocale != null) {
1✔
904
                        return shortNameInLocale;
1✔
905
                }
906
                
907
                ConceptName shortestNameForLocale = null;
1✔
908
                ConceptName shortestNameForConcept = null;
1✔
909
                
910
                if (locale != null) {
1✔
911
                        for (ConceptName possibleName : getNames()) {
1✔
912
                                if (possibleName.getLocale().equals(locale)
1✔
913
                                        && ((shortestNameForLocale == null) || (possibleName.getName().length() < shortestNameForLocale
1✔
914
                                                .getName().length()))) {
1✔
915
                                        shortestNameForLocale = possibleName;
1✔
916
                                }
917
                                if ((shortestNameForConcept == null)
1✔
918
                                        || (possibleName.getName().length() < shortestNameForConcept.getName().length())) {
1✔
919
                                        shortestNameForConcept = possibleName;
1✔
920
                                }
921
                        }
1✔
922
                }
923
                
924
                if (exact) {
1✔
925
                        if (shortestNameForLocale == null) {
1✔
926
                                log.warn("No short concept name found for concept id " + conceptId + " for locale "
1✔
927
                                        + locale.getDisplayName());
1✔
928
                        }
929
                        return shortestNameForLocale;
1✔
930
                }
931
                
932
                return shortestNameForConcept;
1✔
933
        }
934
        
935
        /**
936
         * @param name A name
937
         * @return whether this concept has the given name in any locale
938
         */
939
        public boolean isNamed(String name) {
940
                return getNames().stream().anyMatch(cn -> name.equals(cn.getName()));
1✔
941
        }
942
        
943
        /**
944
         * Gets the list of all non-retired concept names which are index terms for this concept
945
         * 
946
         * @return a collection of concept names which are index terms for this concept
947
         * @since 1.7
948
         */
949
        public Collection<ConceptName> getIndexTerms() {
950
                return getNames().stream()
×
951
                                .filter(ConceptName::isIndexTerm)
×
952
                                .collect(Collectors.toSet());                
×
953
        }
954
        
955
        /**
956
         * Gets the list of all non-retired concept names which are index terms in a given locale
957
         * 
958
         * @param locale the locale for the index terms to return
959
         * @return a collection of concept names which are index terms in the given locale
960
         * @since 1.7
961
         */
962
        public Collection<ConceptName> getIndexTermsForLocale(Locale locale) {
963
                return getIndexTerms().stream()
×
964
                                .filter(n -> n.getLocale().equals(locale))
×
965
                        .collect(Collectors.toList());
×
966
        }
967
        
968
        /**
969
         * @return Returns the names.
970
         */
971
        public Collection<ConceptName> getNames() {
972
                return getNames(false);
1✔
973
        }
974
        
975
        /**
976
         * @return Returns the names.
977
         * @param includeVoided Include voided ConceptNames if true.
978
         */
979
        public Collection<ConceptName> getNames(boolean includeVoided) {
980
                if (names == null) {
1✔
981
                        names = new HashSet<>();
×
982
                }
983

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

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

1637
        /**
1638
         * @see org.openmrs.customdatatype.Customizable#getAttributes()
1639
         */
1640
        @Override
1641
        public Set<ConceptAttribute> getAttributes() {
1642
                if (attributes == null) {
1✔
1643
                        attributes = new LinkedHashSet<>();
×
1644
                }
1645
                return attributes;
1✔
1646
        }
1647

1648
        /**
1649
         * @see org.openmrs.customdatatype.Customizable#getActiveAttributes()
1650
         */
1651
        @Override
1652
        public Collection<ConceptAttribute> getActiveAttributes() {
1653
                return getAttributes().stream()
1✔
1654
                                .filter(attr -> !attr.getVoided())
1✔
1655
                                .collect(Collectors.toList());
1✔
1656
        }
1657

1658
        /**
1659
         * @see org.openmrs.customdatatype.Customizable#getActiveAttributes(org.openmrs.customdatatype.CustomValueDescriptor)
1660
         */
1661
        @Override
1662
        public List<ConceptAttribute> getActiveAttributes(CustomValueDescriptor ofType) {
1663
                return getAttributes().stream()
×
1664
                                .filter(attr -> attr.getAttributeType().equals(ofType) && !attr.getVoided())
×
1665
                                .collect(Collectors.toList());
×
1666
        }
1667

1668
        /**
1669
         * @param attributes the attributes to set
1670
         */
1671
        public void setAttributes(Set<ConceptAttribute> attributes) {
1672
                this.attributes = attributes;
1✔
1673
        }
1✔
1674

1675
        /**
1676
         * @see org.openmrs.customdatatype.Customizable#addAttribute(Attribute)
1677
         */
1678
        @Override
1679
        public void addAttribute(ConceptAttribute attribute) {
1680
                getAttributes().add(attribute);
×
1681
                attribute.setOwner(this);
×
1682
        }
×
1683

1684
}
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

© 2025 Coveralls, Inc