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

openmrs / openmrs-core / 8193954499

07 Mar 2024 07:48PM CUT coverage: 64.702%. Remained the same
8193954499

push

github

web-flow
maven(deps): bump jakarta.xml.bind:jakarta.xml.bind-api (#4578)

Bumps [jakarta.xml.bind:jakarta.xml.bind-api](https://github.com/jakartaee/jaxb-api) from 4.0.0 to 4.0.2.
- [Release notes](https://github.com/jakartaee/jaxb-api/releases)
- [Commits](https://github.com/jakartaee/jaxb-api/compare/4.0.0...4.0.2)

---
updated-dependencies:
- dependency-name: jakarta.xml.bind:jakarta.xml.bind-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

22766 of 35186 relevant lines covered (64.7%)

0.65 hits per line

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

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

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

130
        private Set<ConceptAttribute> attributes = new LinkedHashSet<>();
1✔
131

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

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

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

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

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

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

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

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

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

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