3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.qualityprofile;
22 import com.google.common.io.Resources;
23 import java.io.Reader;
24 import java.io.StringReader;
25 import java.io.StringWriter;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.function.Consumer;
31 import java.util.stream.Stream;
32 import javax.annotation.Nullable;
33 import org.junit.jupiter.api.Test;
34 import org.junit.jupiter.api.extension.RegisterExtension;
35 import org.junit.jupiter.params.ParameterizedTest;
36 import org.junit.jupiter.params.provider.Arguments;
37 import org.junit.jupiter.params.provider.MethodSource;
38 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
39 import org.sonar.api.rule.RuleKey;
40 import org.sonar.api.rule.RuleStatus;
41 import org.sonar.api.rules.RuleType;
42 import org.sonar.api.utils.System2;
43 import org.sonar.core.util.UuidFactoryFast;
44 import org.sonar.db.DbSession;
45 import org.sonar.db.DbTester;
46 import org.sonar.db.qualityprofile.ActiveRuleDto;
47 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
48 import org.sonar.db.qualityprofile.QProfileDto;
49 import org.sonar.db.qualityprofile.QualityProfileTesting;
50 import org.sonar.db.rule.RuleDto;
51 import org.sonar.db.rule.RuleParamDto;
52 import org.sonar.server.qualityprofile.builtin.QProfileName;
53 import org.sonar.server.common.rule.RuleCreator;
55 import static java.nio.charset.StandardCharsets.UTF_8;
56 import static org.assertj.core.api.Assertions.assertThat;
57 import static org.assertj.core.api.Assertions.assertThatThrownBy;
58 import static org.junit.Assert.assertThrows;
59 import static org.mockito.ArgumentMatchers.any;
60 import static org.mockito.ArgumentMatchers.anyList;
61 import static org.mockito.Mockito.mock;
62 import static org.mockito.Mockito.when;
63 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
64 import static org.sonar.db.rule.RuleTesting.newRule;
65 import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
67 class QProfileBackuperImplIT {
69 private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
70 "<profile><name>foo</name>" +
71 "<language>js</language>" +
75 private final System2 system2 = new AlwaysIncreasingSystem2();
78 private final DbTester db = DbTester.create(system2);
80 private final DummyReset reset = new DummyReset();
81 private final QProfileFactory profileFactory = new DummyProfileFactory();
82 private final RuleCreator ruleCreator = mock(RuleCreator.class);
84 private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
87 void backup_generates_xml_file() {
88 RuleDto rule = createRule();
89 QProfileDto profile = createProfile(rule.getLanguage());
90 ActiveRuleDto activeRule = activate(profile, rule, ar -> ar.setPrioritizedRule(false));
92 StringWriter writer = new StringWriter();
93 underTest.backup(db.getSession(), profile, writer);
95 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
96 "<profile><name>" + profile.getName() + "</name>" +
97 "<language>" + profile.getLanguage() + "</language>" +
100 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
101 "<key>" + rule.getRuleKey() + "</key>" +
102 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
103 "<priority>" + activeRule.getSeverityString() + "</priority>" +
104 "<parameters></parameters>" +
111 void backup_prioritized_rule() {
112 RuleDto rule = createRule();
113 QProfileDto profile = createProfile(rule.getLanguage());
114 ActiveRuleDto activeRule = activate(profile, rule, ar -> ar.setPrioritizedRule(true));
116 StringWriter writer = new StringWriter();
117 underTest.backup(db.getSession(), profile, writer);
119 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
120 "<profile><name>" + profile.getName() + "</name>" +
121 "<language>" + profile.getLanguage() + "</language>" +
124 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
125 "<key>" + rule.getRuleKey() + "</key>" +
126 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
127 "<priority>" + activeRule.getSeverityString() + "</priority>" +
128 "<prioritizedRule>true</prioritizedRule>" +
129 "<parameters></parameters>" +
136 void backup_rules_having_parameters() {
137 RuleDto rule = createRule();
138 RuleParamDto param = db.rules().insertRuleParam(rule);
139 QProfileDto profile = createProfile(rule.getLanguage());
140 ActiveRuleDto activeRule = activate(profile, rule, param);
142 StringWriter writer = new StringWriter();
143 underTest.backup(db.getSession(), profile, writer);
145 assertThat(writer.toString()).contains(
147 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
148 "<key>" + rule.getRuleKey() + "</key>" +
149 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
150 "<priority>" + activeRule.getSeverityString() + "</priority>" +
151 "<parameters><parameter>" +
152 "<key>" + param.getName() + "</key>" +
153 "<value>20</value>" +
154 "</parameter></parameters>" +
159 void backup_empty_profile() {
160 RuleDto rule = createRule();
161 QProfileDto profile = createProfile(rule.getLanguage());
163 StringWriter writer = new StringWriter();
164 underTest.backup(db.getSession(), profile, writer);
166 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
167 "<profile><name>" + profile.getName() + "</name>" +
168 "<language>" + profile.getLanguage() + "</language>" +
174 void backup_custom_rules_with_params() {
175 RuleDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
176 .setIsTemplate(true));
177 RuleDto rule = db.rules().insert(
178 newRule(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "custom rule description"))
179 .setName("custom rule name")
180 .setStatus(RuleStatus.READY)
181 .setTemplateUuid(templateRule.getUuid()));
182 RuleParamDto param = db.rules().insertRuleParam(rule);
183 QProfileDto profile = createProfile(rule.getLanguage());
184 ActiveRuleDto activeRule = activate(profile, rule, param);
186 StringWriter writer = new StringWriter();
187 underTest.backup(db.getSession(), profile, writer);
189 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
191 "<name>" + profile.getName() + "</name>" +
192 "<language>" + profile.getLanguage() + "</language>" +
194 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
195 "<key>" + rule.getKey().rule() + "</key>" +
196 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
197 "<priority>" + activeRule.getSeverityString() + "</priority>" +
198 "<name>" + rule.getName() + "</name>" +
199 "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
200 "<description>" + rule.getDefaultRuleDescriptionSection().getContent() + "</description>" +
201 "<parameters><parameter>" +
202 "<key>" + param.getName() + "</key>" +
203 "<value>20</value>" +
204 "</parameter></parameters>" +
205 "</rule></rules></profile>");
209 void backup_custom_rules_without_description_section() {
210 var rule = newRuleWithoutDescriptionSection();
211 db.rules().insert(rule);
212 RuleParamDto param = db.rules().insertRuleParam(rule);
213 QProfileDto profile = createProfile(rule.getLanguage());
214 ActiveRuleDto activeRule = activate(profile, rule, param);
216 StringWriter writer = new StringWriter();
217 underTest.backup(db.getSession(), profile, writer);
219 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
221 "<name>" + profile.getName() + "</name>" +
222 "<language>" + profile.getLanguage() + "</language>" +
224 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
225 "<key>" + rule.getKey().rule() + "</key>" +
226 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
227 "<priority>" + activeRule.getSeverityString() + "</priority>" +
228 "<parameters><parameter>" +
229 "<key>" + param.getName() + "</key>" +
230 "<value>20</value>" +
231 "</parameter></parameters>" +
232 "</rule></rules></profile>");
236 void restore_backup_on_the_profile_specified_in_backup() {
237 Reader backup = new StringReader(EMPTY_BACKUP);
239 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
241 assertThat(summary.profile().getName()).isEqualTo("foo");
242 assertThat(summary.profile().getLanguage()).isEqualTo("js");
244 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.profile().getKee());
245 assertThat(reset.calledActivations).isEmpty();
249 void restore_detects_deprecated_rule_keys() {
250 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
251 db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
253 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
254 "<profile><name>foo</name>" +
255 "<language>js</language>" +
258 "<repositoryKey>oldrepo</repositoryKey>" +
259 "<key>oldkey</key>" +
260 "<priority>BLOCKER</priority>" +
262 "<parameter><key>bar</key><value>baz</value></parameter>" +
268 underTest.restore(db.getSession(), backup, (String) null);
270 assertThat(reset.calledActivations).hasSize(1);
271 RuleActivation activation = reset.calledActivations.get(0);
272 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
273 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
274 assertThat(activation.getParameter("bar")).isEqualTo("baz");
278 void restore_ignores_deprecated_rule_keys_if_new_key_is_already_present() {
279 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
280 db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
282 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
283 "<profile><name>foo</name>" +
284 "<language>js</language>" +
287 "<repositoryKey>oldrepo</repositoryKey>" +
288 "<key>oldkey</key>" +
289 "<priority>MAJOR</priority>" +
291 "<parameter><key>bar</key><value>baz</value></parameter>" +
295 "<repositoryKey>sonarjs</repositoryKey>" +
297 "<priority>BLOCKER</priority>" +
299 "<parameter><key>bar2</key><value>baz2</value></parameter>" +
305 underTest.restore(db.getSession(), backup, (String) null);
307 assertThat(reset.calledActivations).hasSize(1);
308 RuleActivation activation = reset.calledActivations.get(0);
309 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
310 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
311 assertThat(activation.getParameter("bar2")).isEqualTo("baz2");
315 void restore_backup_on_profile_having_different_name() {
316 Reader backup = new StringReader(EMPTY_BACKUP);
318 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
320 assertThat(summary.profile().getName()).isEqualTo("bar");
321 assertThat(summary.profile().getLanguage()).isEqualTo("js");
323 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.profile().getKee());
324 assertThat(reset.calledActivations).isEmpty();
328 void restore_resets_the_activated_rules() {
329 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
330 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
331 "<profile><name>foo</name>" +
332 "<language>js</language>" +
335 "<repositoryKey>sonarjs</repositoryKey>" +
337 "<priority>BLOCKER</priority>" +
338 "<prioritizedRule>true</prioritizedRule>" +
340 "<parameter><key>bar</key><value>baz</value></parameter>" +
346 underTest.restore(db.getSession(), backup, (String) null);
348 assertThat(reset.calledActivations).hasSize(1);
349 RuleActivation activation = reset.calledActivations.get(0);
350 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
351 assertThat(activation.isPrioritizedRule()).isTrue();
352 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
353 assertThat(activation.getParameter("bar")).isEqualTo("baz");
357 @MethodSource("prioritizedRules")
358 void restore_sets_correctly_the_prioritizedRule_flag(boolean prioritizedInProfile, Boolean prioritizedInBackup, boolean expected) {
359 RuleDto rule = createRule();
360 QProfileDto profile = createProfile(rule.getLanguage());
361 ActiveRuleDto activeRule = activate(profile, rule, ar -> ar.setPrioritizedRule(prioritizedInProfile));
363 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
364 "<profile><name>" + profile.getName() + "</name>" +
365 "<language>" + profile.getLanguage() + "</language>" +
368 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
369 "<key>" + rule.getRuleKey() + "</key>" +
370 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
371 "<priority>" + activeRule.getSeverityString() + "</priority>" +
372 (prioritizedInBackup == null ? "" : "<prioritizedRule>" + prioritizedInBackup + "</prioritizedRule>") +
377 underTest.restore(db.getSession(), backup, (String) null);
379 assertThat(reset.calledActivations).hasSize(1);
380 RuleActivation activation = reset.calledActivations.get(0);
381 assertThat(activation.getSeverity()).isEqualTo(activeRule.getSeverityString());
382 assertThat(activation.isPrioritizedRule()).isEqualTo(expected);
383 assertThat(activation.getRuleUuid()).isEqualTo(rule.getUuid());
386 private static Stream<Arguments> prioritizedRules() {
388 Arguments.of(true, false, false),
389 Arguments.of(true, null, false),
390 Arguments.of(true, true, true),
391 Arguments.of(false, false, false),
392 Arguments.of(false, null, false),
393 Arguments.of(false, true, true)
399 void restore_custom_rule() {
400 when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001"))));
402 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
404 "<name>custom rule</name>" +
405 "<language>js</language>" +
407 "<repositoryKey>sonarjs</repositoryKey>" +
409 "<type>CODE_SMELL</type>" +
410 "<priority>CRITICAL</priority>" +
411 "<name>custom rule name</name>" +
412 "<templateKey>rule_mc8</templateKey>" +
413 "<description>custom rule description</description>" +
414 "<parameters><parameter>" +
416 "<value>baz</value>" +
419 "</rule></rules></profile>");
421 underTest.restore(db.getSession(), backup, (String) null);
423 assertThat(reset.calledActivations).hasSize(1);
424 RuleActivation activation = reset.calledActivations.get(0);
425 assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
426 assertThat(activation.getParameter("bar")).isEqualTo("baz");
430 void restore_skips_rule_without_template_key_and_db_definition() {
431 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
432 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
433 "<profile><name>foo</name>" +
434 "<language>js</language>" +
437 "<repositoryKey>sonarjs</repositoryKey>" +
439 "<priority>BLOCKER</priority>" +
441 "<parameter><key>bar</key><value>baz</value></parameter>" +
445 "<repositoryKey>sonarjs</repositoryKey>" +
447 "<priority>MAJOR</priority>" +
452 underTest.restore(db.getSession(), backup, (String) null);
454 assertThat(reset.calledActivations).hasSize(1);
455 RuleActivation activation = reset.calledActivations.get(0);
456 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
457 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
458 assertThat(activation.getParameter("bar")).isEqualTo("baz");
462 void copy_profile() {
463 RuleDto rule = createRule();
464 RuleParamDto param = db.rules().insertRuleParam(rule);
465 QProfileDto from = createProfile(rule.getLanguage());
466 ActiveRuleDto activeRule = activate(from, rule, param);
468 QProfileDto to = createProfile(rule.getLanguage());
469 underTest.copy(db.getSession(), from, to);
471 assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
472 assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
473 assertThat(reset.calledProfile).isEqualTo(to);
477 void copy_profile_with_custom_rule() {
478 RuleDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
479 .setIsTemplate(true));
480 RuleDto rule = db.rules().insert(
481 newRule(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "custom rule description"))
482 .setName("custom rule name")
483 .setStatus(RuleStatus.READY)
484 .setTemplateUuid(templateRule.getUuid()));
486 RuleParamDto param = db.rules().insertRuleParam(rule);
487 QProfileDto from = createProfile(rule.getLanguage());
488 ActiveRuleDto activeRule = activate(from, rule, param);
490 QProfileDto to = createProfile(rule.getLanguage());
491 underTest.copy(db.getSession(), from, to);
493 assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
494 assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
495 assertThat(reset.calledProfile).isEqualTo(to);
499 void fail_to_restore_if_bad_xml_format() {
500 DbSession session = db.getSession();
501 StringReader backup = new StringReader("<rules><rule></rules>");
502 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
503 assertThat(thrown).hasMessage("Backup XML is not valid. Root element must be <profile>.");
504 assertThat(reset.calledProfile).isNull();
508 void fail_to_restore_if_not_xml_backup() {
509 DbSession session = db.getSession();
510 StringReader backup = new StringReader("foo");
511 assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
512 assertThat(reset.calledProfile).isNull();
516 void fail_to_restore_if_xml_is_not_well_formed() {
517 assertThatThrownBy(() -> {
518 String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
520 underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
522 .isInstanceOf(IllegalArgumentException.class)
523 .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
527 void fail_to_restore_if_duplicate_rule() throws Exception {
528 DbSession session = db.getSession();
529 String xml = Resources.toString(getClass().getResource("QProfileBackuperIT/duplicates-xml-backup.xml"), UTF_8);
530 StringReader backup = new StringReader(xml);
531 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
532 assertThat(thrown).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
533 assertThat(reset.calledProfile).isNull();
537 void fail_to_restore_external_rule() {
538 db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
539 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
540 "<profile><name>foo</name>" +
541 "<language>js</language>" +
544 "<repositoryKey>sonarjs</repositoryKey>" +
546 "<priority>BLOCKER</priority>" +
548 "<parameter><key>bar</key><value>baz</value></parameter>" +
554 assertThatThrownBy(() -> {
555 underTest.restore(db.getSession(), backup, (String) null);
557 .isInstanceOf(IllegalArgumentException.class)
558 .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
561 private RuleDto createRule() {
562 return db.rules().insert();
565 private QProfileDto createProfile(String language) {
566 return db.qualityProfiles().insert(p -> p.setLanguage(language));
569 private ActiveRuleDto activate(QProfileDto profile, RuleDto rule) {
570 return db.qualityProfiles().activateRule(profile, rule);
573 private ActiveRuleDto activate(QProfileDto profile, RuleDto rule, Consumer<ActiveRuleDto> consumer) {
574 return db.qualityProfiles().activateRule(profile, rule, consumer);
577 private ActiveRuleDto activate(QProfileDto profile, RuleDto rule, RuleParamDto param) {
578 ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule, ar -> ar.setPrioritizedRule(false));
579 ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
581 .setActiveRuleUuid(activeRule.getUuid());
582 db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
586 private static class DummyReset implements QProfileReset {
587 private QProfileDto calledProfile;
588 private List<RuleActivation> calledActivations;
591 public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
592 this.calledProfile = profile;
593 this.calledActivations = new ArrayList<>(activations);
594 return new BulkChangeResult();
598 private static class DummyProfileFactory implements QProfileFactory {
600 public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
601 return QualityProfileTesting.newQualityProfileDto()
602 .setLanguage(key.getLanguage())
603 .setName(key.getName());
607 public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
608 throw new UnsupportedOperationException();
612 public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
613 throw new UnsupportedOperationException();
617 public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
618 throw new UnsupportedOperationException();