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

jreleaser / jreleaser / #558

08 Dec 2025 02:56PM UTC coverage: 48.239% (+0.02%) from 48.215%
#558

push

github

aalmiray
feat(core): warn when a name template cannot be resolved

Closes #1960

Closes #1961

299 of 573 new or added lines in 133 files covered. (52.18%)

4 existing lines in 4 files now uncovered.

26047 of 53996 relevant lines covered (48.24%)

0.48 hits per line

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

61.32
/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/project/Project.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.model.internal.project;
19

20
import com.fasterxml.jackson.annotation.JsonIgnore;
21
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
22
import com.github.mustachejava.TemplateFunction;
23
import org.jreleaser.bundle.RB;
24
import org.jreleaser.model.Active;
25
import org.jreleaser.model.Constants;
26
import org.jreleaser.model.JReleaserException;
27
import org.jreleaser.model.Stereotype;
28
import org.jreleaser.model.internal.JReleaserContext;
29
import org.jreleaser.model.internal.common.AbstractModelObject;
30
import org.jreleaser.model.internal.common.Domain;
31
import org.jreleaser.model.internal.common.ExtraProperties;
32
import org.jreleaser.model.internal.common.Icon;
33
import org.jreleaser.model.internal.common.Java;
34
import org.jreleaser.model.internal.common.Screenshot;
35
import org.jreleaser.mustache.MustacheUtils;
36
import org.jreleaser.mustache.TemplateContext;
37
import org.jreleaser.util.Env;
38
import org.jreleaser.util.PlatformUtils;
39
import org.jreleaser.version.CalVer;
40
import org.jreleaser.version.ChronVer;
41
import org.jreleaser.version.CustomVersion;
42
import org.jreleaser.version.JavaModuleVersion;
43
import org.jreleaser.version.JavaRuntimeVersion;
44
import org.jreleaser.version.SemanticVersion;
45
import org.jreleaser.version.Version;
46

47
import java.io.Serializable;
48
import java.util.ArrayList;
49
import java.util.Collection;
50
import java.util.LinkedHashMap;
51
import java.util.List;
52
import java.util.Map;
53

54
import static java.util.Collections.unmodifiableList;
55
import static java.util.Collections.unmodifiableMap;
56
import static java.util.stream.Collectors.toList;
57
import static org.jreleaser.model.JReleaserOutput.nag;
58
import static org.jreleaser.model.api.project.Project.DEFAULT_SNAPSHOT_PATTERN;
59
import static org.jreleaser.model.api.project.Project.PROJECT_NAME;
60
import static org.jreleaser.model.api.project.Project.PROJECT_SNAPSHOT_LABEL;
61
import static org.jreleaser.model.api.project.Project.PROJECT_SNAPSHOT_PATTERN;
62
import static org.jreleaser.model.api.project.Project.PROJECT_VERSION;
63
import static org.jreleaser.mustache.MustacheUtils.applyTemplates;
64
import static org.jreleaser.mustache.Templates.resolveTemplate;
65
import static org.jreleaser.util.StringUtils.getCapitalizedName;
66
import static org.jreleaser.util.StringUtils.isBlank;
67
import static org.jreleaser.util.StringUtils.isNotBlank;
68

69
/**
70
 * @author Andres Almiray
71
 * @since 0.1.0
72
 */
73
public final class Project extends AbstractModelObject<Project> implements Domain, ExtraProperties, Active.Releaseable {
1✔
74
    private static final long serialVersionUID = 7080958109557667812L;
75

76
    private final List<String> authors = new ArrayList<>();
1✔
77
    private final List<String> tags = new ArrayList<>();
1✔
78
    private final List<String> maintainers = new ArrayList<>();
1✔
79
    private final Map<String, Object> extraProperties = new LinkedHashMap<>();
1✔
80
    private final Links links = new Links();
1✔
81
    private final Languages languages = new Languages();
1✔
82
    private final Snapshot snapshot = new Snapshot();
1✔
83
    private final List<Screenshot> screenshots = new ArrayList<>();
1✔
84
    private final List<Icon> icons = new ArrayList<>();
1✔
85
    private String name;
86
    private String version;
87
    private VersionPattern versionPattern = new VersionPattern();
1✔
88
    private String description;
89
    private String longDescription;
90
    private String license;
91
    private String inceptionYear;
92
    private String copyright;
93
    private String vendor;
94
    private Stereotype stereotype = Stereotype.NONE;
1✔
95

96
    @JsonIgnore
1✔
97
    private final org.jreleaser.model.api.project.Project immutable = new org.jreleaser.model.api.project.Project() {
1✔
98
        private static final long serialVersionUID = -2581431031545327120L;
99

100
        private List<? extends org.jreleaser.model.api.common.Screenshot> screenshots;
101
        private List<? extends org.jreleaser.model.api.common.Icon> icons;
102

103
        @Override
104
        public boolean isSnapshot() {
105
            return Project.this.isSnapshot();
×
106
        }
107

108
        @Override
109
        public boolean isRelease() {
110
            return Project.this.isRelease();
×
111
        }
112

113
        @Override
114
        public String getName() {
115
            return name;
×
116
        }
117

118
        @Override
119
        public String getVersion() {
120
            return version;
1✔
121
        }
122

123
        @Override
124
        public String getVersionPattern() {
125
            return versionPattern.toString();
×
126
        }
127

128
        @Override
129
        public Snapshot getSnapshot() {
130
            return snapshot.asImmutable();
×
131
        }
132

133
        @Override
134
        public String getDescription() {
135
            return description;
×
136
        }
137

138
        @Override
139
        public String getLongDescription() {
140
            return longDescription;
×
141
        }
142

143
        @Override
144
        public String getWebsite() {
145
            return Project.this.getWebsite();
×
146
        }
147

148
        @Override
149
        public String getLicense() {
150
            return license;
×
151
        }
152

153
        @Override
154
        public String getLicenseUrl() {
155
            return Project.this.getLicenseUrl();
×
156
        }
157

158
        @Override
159
        public String getInceptionYear() {
160
            return inceptionYear;
×
161
        }
162

163
        @Override
164
        public String getCopyright() {
165
            return copyright;
×
166
        }
167

168
        @Override
169
        public String getVendor() {
170
            return vendor;
×
171
        }
172

173
        @Override
174
        public String getDocsUrl() {
175
            return Project.this.getDocsUrl();
×
176
        }
177

178
        @Override
179
        public Stereotype getStereotype() {
180
            return stereotype;
×
181
        }
182

183
        @Override
184
        public List<? extends org.jreleaser.model.api.common.Screenshot> getScreenshots() {
185
            if (null == screenshots) {
×
186
                screenshots = Project.this.screenshots.stream()
×
187
                    .map(Screenshot::asImmutable)
×
188
                    .collect(toList());
×
189
            }
190
            return screenshots;
×
191
        }
192

193
        @Override
194
        public List<? extends org.jreleaser.model.api.common.Icon> getIcons() {
195
            if (null == icons) {
×
196
                icons = Project.this.icons.stream()
×
197
                    .map(Icon::asImmutable)
×
198
                    .collect(toList());
×
199
            }
200
            return icons;
×
201
        }
202

203
        @Deprecated
204
        @Override
205
        public org.jreleaser.model.api.common.Java getJava() {
206
            return languages.getJava().asImmutable();
×
207
        }
208

209
        @Override
210
        public org.jreleaser.model.api.project.Languages getLanguages() {
211
            return languages.asImmutable();
×
212
        }
213

214
        @Override
215
        public List<String> getAuthors() {
216
            return unmodifiableList(authors);
×
217
        }
218

219
        @Override
220
        public List<String> getTags() {
221
            return unmodifiableList(tags);
×
222
        }
223

224
        @Override
225
        public List<String> getMaintainers() {
226
            return unmodifiableList(maintainers);
×
227
        }
228

229
        @Override
230
        public Links getLinks() {
231
            return links.asImmutable();
×
232
        }
233

234
        @Override
235
        public Version<?> version() {
236
            return Project.this.version();
×
237
        }
238

239
        @Override
240
        public Map<String, Object> asMap(boolean full) {
241
            return unmodifiableMap(Project.this.asMap(full));
×
242
        }
243

244
        @Override
245
        public String getPrefix() {
246
            return Project.this.prefix();
×
247
        }
248

249
        @Override
250
        public Map<String, Object> getExtraProperties() {
251
            return unmodifiableMap(extraProperties);
×
252
        }
253
    };
254

255
    public org.jreleaser.model.api.project.Project asImmutable() {
256
        return immutable;
1✔
257
    }
258

259
    @Override
260
    public void merge(Project source) {
261
        this.name = merge(this.name, source.name);
1✔
262
        this.version = merge(this.version, source.version);
1✔
263
        this.versionPattern = merge(this.versionPattern, source.versionPattern);
1✔
264
        this.description = merge(this.description, source.description);
1✔
265
        this.longDescription = merge(this.longDescription, source.longDescription);
1✔
266
        this.license = merge(this.license, source.license);
1✔
267
        this.inceptionYear = merge(this.inceptionYear, source.inceptionYear);
1✔
268
        this.copyright = merge(this.copyright, source.copyright);
1✔
269
        this.vendor = merge(this.vendor, source.vendor);
1✔
270
        this.stereotype = merge(this.stereotype, source.stereotype);
1✔
271
        setLanguages(source.languages);
1✔
272
        setSnapshot(source.snapshot);
1✔
273
        setAuthors(merge(this.authors, source.authors));
1✔
274
        setTags(merge(this.tags, source.tags));
1✔
275
        setMaintainers(merge(this.maintainers, source.maintainers));
1✔
276
        setExtraProperties(merge(this.extraProperties, source.extraProperties));
1✔
277
        setLinks(source.links);
1✔
278
        setScreenshots(merge(this.screenshots, source.screenshots));
1✔
279
        setIcons(merge(this.icons, source.icons));
1✔
280
    }
1✔
281

282
    @Override
283
    public String prefix() {
284
        return "project";
1✔
285
    }
286

287
    public String getEffectiveVersion() {
288
        if (isSnapshot()) {
1✔
289
            return getSnapshot().getEffectiveLabel();
×
290
        }
291

292
        return getResolvedVersion();
1✔
293
    }
294

295
    public boolean isSnapshot() {
296
        return snapshot.isSnapshot(getResolvedVersion());
1✔
297
    }
298

299
    @Override
300
    public boolean isRelease() {
301
        return !isSnapshot();
1✔
302
    }
303

304
    public String getResolvedName() {
305
        return Env.env(PROJECT_NAME, name);
1✔
306
    }
307

308
    public String getResolvedVersion() {
309
        String resolvedVersion = Env.env(PROJECT_VERSION, version);
1✔
310
        // prevent NPE during validation
311
        return isNotBlank(resolvedVersion) ? resolvedVersion : "";
1✔
312
    }
313

314
    public String getName() {
315
        return name;
1✔
316
    }
317

318
    public void setName(String name) {
319
        this.name = name;
1✔
320
    }
1✔
321

322
    public String getVersion() {
323
        return version;
1✔
324
    }
325

326
    public void setVersion(String version) {
327
        this.version = version;
1✔
328
    }
1✔
329

330
    public String getVersionPattern() {
331
        return null != versionPattern ? versionPattern.toString() : "";
1✔
332
    }
333

334
    public void setVersionPattern(VersionPattern versionPattern) {
335
        this.versionPattern.merge(versionPattern);
1✔
336
    }
1✔
337

338
    public void setVersionPattern(String str) {
339
        setVersionPattern(VersionPattern.of(str));
1✔
340
    }
1✔
341

342
    public VersionPattern versionPattern() {
343
        return versionPattern;
1✔
344
    }
345

346
    public Snapshot getSnapshot() {
347
        return snapshot;
1✔
348
    }
349

350
    public void setSnapshot(Snapshot snapshot) {
351
        this.snapshot.merge(snapshot);
1✔
352
    }
1✔
353

354
    public String getDescription() {
355
        return description;
1✔
356
    }
357

358
    public void setDescription(String description) {
359
        this.description = description;
1✔
360
    }
1✔
361

362
    public String getLongDescription() {
363
        return longDescription;
1✔
364
    }
365

366
    public void setLongDescription(String longDescription) {
367
        this.longDescription = longDescription;
1✔
368
    }
1✔
369

370
    @Deprecated
371
    @JsonPropertyDescription("project.website is deprecated since 1.2.0 and will be removed in 2.0.0. Use project.links.homepage instead")
372
    public String getWebsite() {
373
        return links.getHomepage();
×
374
    }
375

376
    @Deprecated
377
    public void setWebsite(String website) {
378
        nag("project.website is deprecated since 1.2.0 and will be removed in 2.0.0. Use project.links.homepage instead");
×
379
        links.setHomepage(website);
×
380
    }
×
381

382
    public String getLicense() {
383
        return license;
1✔
384
    }
385

386
    public void setLicense(String license) {
387
        this.license = license;
1✔
388
    }
1✔
389

390
    @Deprecated
391
    @JsonPropertyDescription("project.licenseUrl is deprecated since 1.2.0 and will be removed in 2.0.0. Use project.links.license instead")
392
    public String getLicenseUrl() {
393
        return links.getLicense();
×
394
    }
395

396
    @Deprecated
397
    public void setLicenseUrl(String licenseUrl) {
398
        nag("project.licenseUrl is deprecated since 1.2.0 and will be removed in 2.0.0. Use project.links.license instead");
×
399
        links.setLicense(licenseUrl);
×
400
    }
×
401

402
    public String getInceptionYear() {
403
        return inceptionYear;
1✔
404
    }
405

406
    public void setInceptionYear(String inceptionYear) {
407
        this.inceptionYear = inceptionYear;
1✔
408
    }
1✔
409

410
    public String getCopyright() {
411
        return copyright;
1✔
412
    }
413

414
    public void setCopyright(String copyright) {
415
        this.copyright = copyright;
1✔
416
    }
1✔
417

418
    public String getVendor() {
419
        return vendor;
1✔
420
    }
421

422
    public void setVendor(String vendor) {
423
        this.vendor = vendor;
1✔
424
    }
1✔
425

426
    @Deprecated
427
    @JsonPropertyDescription("project.docsUrl is deprecated since 1.2.0 and will be removed in 2.0.0. Use project.links.documentation instead")
428
    public String getDocsUrl() {
429
        return links.getDocumentation();
×
430
    }
431

432
    @Deprecated
433
    public void setDocsUrl(String docsUrl) {
434
        nag("project.docsUrl is deprecated since 1.2.0 and will be removed in 2.0.0. Use project.links.documentation instead");
×
435
        links.setDocumentation(docsUrl);
×
436
    }
×
437

438
    public Stereotype getStereotype() {
439
        return stereotype;
1✔
440
    }
441

442
    public void setStereotype(Stereotype stereotype) {
443
        this.stereotype = stereotype;
1✔
444
    }
1✔
445

446
    public void setStereotype(String str) {
447
        setStereotype(Stereotype.of(str));
1✔
448
    }
1✔
449

450
    public List<Screenshot> getScreenshots() {
451
        return screenshots;
1✔
452
    }
453

454
    public void setScreenshots(List<Screenshot> screenshots) {
455
        this.screenshots.clear();
1✔
456
        this.screenshots.addAll(screenshots);
1✔
457
    }
1✔
458

459
    public void addScreenshot(Screenshot screenshot) {
460
        if (null != screenshot) {
×
461
            this.screenshots.add(screenshot);
×
462
        }
463
    }
×
464

465
    public List<Icon> getIcons() {
466
        return icons;
1✔
467
    }
468

469
    public void setIcons(List<Icon> icons) {
470
        this.icons.clear();
1✔
471
        this.icons.addAll(icons);
1✔
472
    }
1✔
473

474
    public void addIcon(Icon icon) {
475
        if (null != icon) {
×
476
            this.icons.add(icon);
×
477
        }
478
    }
×
479

480
    @Deprecated
481
    @JsonPropertyDescription("project.java is deprecated since 1.16.0 and will be removed in 2.0.0. Use project.languages.java instead")
482
    public Java getJava() {
483
        return languages.getJava();
×
484
    }
485

486
    @Deprecated
487
    public void setJava(Java java) {
488
        nag("project.java is deprecated since 1.16.0 and will be removed in 2.0.0. Use project.languages.java instead");
1✔
489
        this.languages.getJava().merge(java);
1✔
490
    }
1✔
491

492
    public Languages getLanguages() {
493
        return languages;
1✔
494
    }
495

496
    public void setLanguages(Languages languages) {
497
        this.languages.merge(languages);
1✔
498
    }
1✔
499

500
    @Override
501
    public Map<String, Object> getExtraProperties() {
502
        return extraProperties;
1✔
503
    }
504

505
    @Override
506
    public void setExtraProperties(Map<String, Object> extraProperties) {
507
        this.extraProperties.clear();
1✔
508
        this.extraProperties.putAll(extraProperties);
1✔
509
    }
1✔
510

511
    @Override
512
    public void addExtraProperties(Map<String, Object> extraProperties) {
513
        this.extraProperties.putAll(extraProperties);
×
514
    }
×
515

516
    public List<String> getAuthors() {
517
        return authors;
1✔
518
    }
519

520
    public void setAuthors(List<String> authors) {
521
        this.authors.clear();
1✔
522
        this.authors.addAll(authors);
1✔
523
    }
1✔
524

525
    public List<String> getTags() {
526
        return tags;
1✔
527
    }
528

529
    public void setTags(List<String> tags) {
530
        this.tags.clear();
1✔
531
        this.tags.addAll(tags);
1✔
532
    }
1✔
533

534
    public List<String> getMaintainers() {
535
        return maintainers;
1✔
536
    }
537

538
    public void setMaintainers(List<String> maintainers) {
539
        this.maintainers.clear();
1✔
540
        this.maintainers.addAll(maintainers);
1✔
541
    }
1✔
542

543
    public Links getLinks() {
544
        return links;
1✔
545
    }
546

547
    public void setLinks(Links links) {
548
        this.links.merge(links);
1✔
549
    }
1✔
550

551
    @Override
552
    public Map<String, Object> asMap(boolean full) {
553
        Map<String, Object> map = new LinkedHashMap<>();
1✔
554
        map.put("name", name);
1✔
555
        map.put("version", version);
1✔
556
        map.put("versionPattern", versionPattern);
1✔
557
        map.put("snapshot", snapshot.asMap(full));
1✔
558
        map.put("description", description);
1✔
559
        map.put("longDescription", longDescription);
1✔
560
        map.put("license", license);
1✔
561
        map.put("inceptionYear", inceptionYear);
1✔
562
        map.put("copyright", copyright);
1✔
563
        map.put("vendor", vendor);
1✔
564
        map.put("authors", authors);
1✔
565
        map.put("tags", tags);
1✔
566
        map.put("maintainers", maintainers);
1✔
567
        map.put("stereotype", stereotype);
1✔
568
        map.put("links", links.asMap(full));
1✔
569
        Map<String, Map<String, Object>> sm = new LinkedHashMap<>();
1✔
570
        int i = 0;
1✔
571
        for (Screenshot screenshot : screenshots) {
1✔
572
            sm.put("screenshot " + (i++), screenshot.asMap(full));
1✔
573
        }
1✔
574
        map.put("screenshots", sm);
1✔
575
        sm = new LinkedHashMap<>();
1✔
576
        i = 0;
1✔
577
        for (Icon icon : icons) {
1✔
578
            sm.put("icon " + (i++), icon.asMap(full));
1✔
579
        }
1✔
580
        map.put("icons", sm);
1✔
581
        map.put("extraProperties", getExtraProperties());
1✔
582
        if (languages.isEnabled() || full) {
1✔
583
            map.put("languages", languages.asMap(full));
1✔
584
        }
585
        return map;
1✔
586
    }
587

588
    public void parseVersion() {
589
        String v = getResolvedVersion();
1✔
590
        if (isBlank(v)) return;
1✔
591

592
        switch (versionPattern().getType()) {
1✔
593
            case SEMVER:
594
                try {
595
                    SemanticVersion parsedVersion = SemanticVersion.of(v);
1✔
596
                    StringBuilder vn = new StringBuilder().append(parsedVersion.getMajor());
1✔
597
                    addExtraProperty(Constants.KEY_VERSION_MAJOR, parsedVersion.getMajor());
1✔
598
                    if (parsedVersion.hasMinor()) {
1✔
599
                        vn.append(".").append(parsedVersion.getMinor());
1✔
600
                        addExtraProperty(Constants.KEY_VERSION_MINOR, parsedVersion.getMinor());
1✔
601
                    }
602
                    if (parsedVersion.hasPatch()) {
1✔
603
                        vn.append(".").append(parsedVersion.getPatch());
1✔
604
                        addExtraProperty(Constants.KEY_VERSION_PATCH, parsedVersion.getPatch());
1✔
605
                    }
606
                    addExtraProperty(Constants.KEY_VERSION_NUMBER, vn.toString());
1✔
607
                    if (parsedVersion.hasTag()) {
1✔
608
                        addExtraProperty(Constants.KEY_VERSION_TAG, parsedVersion.getTag());
×
609
                    }
610
                    if (parsedVersion.hasBuild()) {
1✔
611
                        addExtraProperty(Constants.KEY_VERSION_BUILD, parsedVersion.getBuild());
×
612
                    }
613
                } catch (IllegalArgumentException e) {
×
614
                    throw new JReleaserException(RB.$("ERROR_version_invalid", v, "semver"), e);
×
615
                }
1✔
616
                break;
617
            case JAVA_RUNTIME:
618
                try {
619
                    JavaRuntimeVersion parsedVersion = JavaRuntimeVersion.of(v);
×
620
                    addExtraProperty(Constants.KEY_VERSION_NUMBER, parsedVersion.getVersion());
×
621
                    if (parsedVersion.hasPrerelease()) {
×
622
                        addExtraProperty(Constants.KEY_VERSION_PRERELEASE, parsedVersion.getPrerelease());
×
623
                    }
624
                    if (parsedVersion.hasBuild()) {
×
625
                        addExtraProperty(Constants.KEY_VERSION_BUILD, parsedVersion.getBuild());
×
626
                    }
627
                    if (parsedVersion.hasOptional()) {
×
628
                        addExtraProperty(Constants.KEY_VERSION_OPTIONAL, parsedVersion.getOptional());
×
629
                    }
630
                } catch (IllegalArgumentException e) {
×
631
                    throw new JReleaserException(RB.$("ERROR_version_invalid", v, "Java runtime"), e);
×
632
                }
×
633
                break;
634
            case JAVA_MODULE:
635
                try {
636
                    JavaModuleVersion parsedVersion = JavaModuleVersion.of(v);
×
637
                    addExtraProperty(Constants.KEY_VERSION_NUMBER, parsedVersion.getVersion());
×
638
                    if (parsedVersion.hasPrerelease()) {
×
639
                        addExtraProperty(Constants.KEY_VERSION_PRERELEASE, parsedVersion.getPrerelease());
×
640
                    }
641
                    if (parsedVersion.hasBuild()) {
×
642
                        addExtraProperty(Constants.KEY_VERSION_BUILD, parsedVersion.getBuild());
×
643
                    }
644
                } catch (IllegalArgumentException e) {
×
645
                    throw new JReleaserException(RB.$("ERROR_version_invalid", v, "Java module"), e);
×
646
                }
×
647
                break;
648
            case CALVER:
649
                try {
650
                    CalVer parsedVersion = CalVer.of(versionPattern().getFormat(), v);
×
651
                    addExtraProperty(Constants.KEY_VERSION_NUMBER, v);
×
652
                    addExtraProperty(Constants.KEY_VERSION_YEAR, parsedVersion.getYear());
×
653
                    if (parsedVersion.hasMonth()) {
×
654
                        addExtraProperty(Constants.KEY_VERSION_MONTH, parsedVersion.getMonth());
×
655
                    }
656
                    if (parsedVersion.hasDay()) {
×
657
                        addExtraProperty(Constants.KEY_VERSION_DAY, parsedVersion.getDay());
×
658
                    }
659
                    if (parsedVersion.hasWeek()) {
×
660
                        addExtraProperty(Constants.KEY_VERSION_WEEK, parsedVersion.getWeek());
×
661
                    }
662
                    if (parsedVersion.hasMinor()) {
×
663
                        addExtraProperty(Constants.KEY_VERSION_MINOR, parsedVersion.getMinor());
×
664
                    }
665
                    if (parsedVersion.hasMicro()) {
×
666
                        addExtraProperty(Constants.KEY_VERSION_MICRO, parsedVersion.getMicro());
×
667
                    }
668
                    if (parsedVersion.hasModifier()) {
×
669
                        addExtraProperty(Constants.KEY_VERSION_MODIFIER, parsedVersion.getModifier());
×
670
                    }
671
                } catch (IllegalArgumentException e) {
×
672
                    throw new JReleaserException(RB.$("ERROR_version_invalid", v, "calver"), e);
×
673
                }
×
674
                break;
675
            case CHRONVER:
676
                try {
677
                    ChronVer parsedVersion = ChronVer.of(v);
×
678
                    addExtraProperty(Constants.KEY_VERSION_NUMBER, v);
×
679
                    addExtraProperty(Constants.KEY_VERSION_YEAR, parsedVersion.getYear());
×
680
                    addExtraProperty(Constants.KEY_VERSION_MONTH, parsedVersion.getMonth());
×
681
                    addExtraProperty(Constants.KEY_VERSION_DAY, parsedVersion.getDay());
×
682
                    if (parsedVersion.hasChangeset()) {
×
683
                        addExtraProperty(Constants.KEY_VERSION_MODIFIER, parsedVersion.getChangeset().toString());
×
684
                    }
685
                } catch (IllegalArgumentException e) {
×
686
                    throw new JReleaserException(RB.$("ERROR_version_invalid", v, "chronver"), e);
×
687
                }
×
688
                break;
689
            default:
690
                addExtraProperty(Constants.KEY_VERSION_NUMBER, v);
×
691
                // noop
692
        }
693

694
        String vn = (String) getExtraProperties().get(Constants.KEY_VERSION_NUMBER);
1✔
695
        String ev = getEffectiveVersion();
1✔
696
        addExtraProperty(Constants.KEY_VERSION_WITH_UNDERSCORES, underscore(v));
1✔
697
        addExtraProperty(Constants.KEY_VERSION_WITH_DASHES, dash(v));
1✔
698
        addExtraProperty(Constants.KEY_VERSION_NUMBER_WITH_UNDERSCORES, underscore(vn));
1✔
699
        addExtraProperty(Constants.KEY_VERSION_NUMBER_WITH_DASHES, dash(vn));
1✔
700
        if (isNotBlank(ev)) {
1✔
701
            addExtraProperty(Constants.KEY_EFFECTIVE_VERSION_WITH_UNDERSCORES, underscore(ev));
1✔
702
            addExtraProperty(Constants.KEY_EFFECTIVE_VERSION_WITH_DASHES, dash(ev));
1✔
703
        }
704
    }
1✔
705

706
    private String underscore(String input) {
707
        return input.replace(".", "_")
1✔
708
            .replace("-", "_")
1✔
709
            .replace("+", "_");
1✔
710
    }
711

712
    private String dash(String input) {
713
        return input.replace(".", "-")
1✔
714
            .replace("_", "-")
1✔
715
            .replace("+", "-");
1✔
716
    }
717

718
    public Version<?> version() {
719
        String v = getResolvedVersion();
1✔
720
        switch (versionPattern().getType()) {
1✔
721
            case SEMVER:
722
                return SemanticVersion.of(v);
1✔
723
            case JAVA_RUNTIME:
724
                return JavaRuntimeVersion.of(v);
×
725
            case JAVA_MODULE:
726
                return JavaModuleVersion.of(v);
×
727
            case CALVER:
728
                return CalVer.of(versionPattern().getFormat(), v);
×
729
            case CHRONVER:
730
                return ChronVer.of(v);
×
731
            case CUSTOM:
732
            default:
733
                return CustomVersion.of(v);
×
734
        }
735
    }
736

737
    public static class Snapshot extends AbstractModelObject<Snapshot> implements Domain {
1✔
738
        private static final long serialVersionUID = 505258495859356548L;
739

740
        private Boolean enabled;
741
        private String pattern;
742
        private String label;
743
        private Boolean fullChangelog;
744
        @JsonIgnore
745
        private String cachedLabel;
746

747
        @JsonIgnore
1✔
748
        private final org.jreleaser.model.api.project.Project.Snapshot immutable = new org.jreleaser.model.api.project.Project.Snapshot() {
1✔
749
            private static final long serialVersionUID = 2581314557970795502L;
750

751
            @Override
752
            public String getPattern() {
753
                return pattern;
×
754
            }
755

756
            @Override
757
            public String getLabel() {
758
                return label;
×
759
            }
760

761
            @Override
762
            public boolean isFullChangelog() {
763
                return Snapshot.this.isFullChangelog();
×
764
            }
765

766
            @Override
767
            public Map<String, Object> asMap(boolean full) {
768
                return unmodifiableMap(Snapshot.this.asMap(full));
×
769
            }
770
        };
771

772
        public org.jreleaser.model.api.project.Project.Snapshot asImmutable() {
773
            return immutable;
×
774
        }
775

776
        @Override
777
        public void merge(Snapshot source) {
778
            this.enabled = this.merge(this.enabled, source.enabled);
1✔
779
            this.pattern = this.merge(this.pattern, source.pattern);
1✔
780
            this.label = this.merge(this.label, source.label);
1✔
781
            this.fullChangelog = this.merge(this.fullChangelog, source.fullChangelog);
1✔
782
        }
1✔
783

784
        public boolean isSnapshot(String version) {
785
            if (null == enabled) {
1✔
786
                enabled = version.matches(getResolvedPattern());
1✔
787
            }
788
            return enabled;
1✔
789
        }
790

791
        public String getConfiguredPattern() {
792
            return Env.env(PROJECT_SNAPSHOT_PATTERN, pattern);
1✔
793
        }
794

795
        public String getResolvedPattern() {
796
            pattern = getConfiguredPattern();
1✔
797
            if (isBlank(pattern)) {
1✔
798
                pattern = DEFAULT_SNAPSHOT_PATTERN;
×
799
            }
800
            return pattern;
1✔
801
        }
802

803
        public String getConfiguredLabel() {
804
            return Env.env(PROJECT_SNAPSHOT_LABEL, label);
1✔
805
        }
806

807
        public String getResolvedLabel(JReleaserContext context) {
808
            if (isBlank(cachedLabel)) {
1✔
809
                cachedLabel = getConfiguredLabel();
1✔
810
            }
811

812
            if (isBlank(cachedLabel)) {
1✔
NEW
813
                cachedLabel = resolveTemplate(context.getLogger(), label, props(context));
×
814
            } else if (cachedLabel.contains("{{")) {
1✔
NEW
815
                cachedLabel = resolveTemplate(context.getLogger(), cachedLabel, props(context));
×
816
            }
817

818
            return cachedLabel;
1✔
819
        }
820

821
        public String getEffectiveLabel() {
822
            return cachedLabel;
×
823
        }
824

825
        public String getPattern() {
826
            return pattern;
1✔
827
        }
828

829
        public void setPattern(String pattern) {
830
            this.pattern = pattern;
1✔
831
        }
1✔
832

833
        public String getLabel() {
834
            return label;
1✔
835
        }
836

837
        public void setLabel(String label) {
838
            this.label = label;
1✔
839
        }
1✔
840

841
        public Boolean getFullChangelog() {
842
            return fullChangelog;
1✔
843
        }
844

845
        public boolean isFullChangelog() {
846
            return null != fullChangelog && fullChangelog;
1✔
847
        }
848

849
        public void setFullChangelog(Boolean fullChangelog) {
850
            this.fullChangelog = fullChangelog;
1✔
851
        }
1✔
852

853
        public boolean isFullChangelogSet() {
854
            return null != fullChangelog;
×
855
        }
856

857
        @Override
858
        public Map<String, Object> asMap(boolean full) {
859
            Map<String, Object> map = new LinkedHashMap<>();
1✔
860
            map.put("enabled", enabled);
1✔
861
            map.put("pattern", getConfiguredPattern());
1✔
862
            map.put("label", getConfiguredLabel());
1✔
863
            map.put("fullChangelog", isFullChangelog());
1✔
864
            return map;
1✔
865
        }
866

867
        public TemplateContext props(JReleaserContext context) {
868
            // duplicate from JReleaserModel to avoid endless recursion
869
            TemplateContext props = new TemplateContext();
×
NEW
870
            Project project = context.getModel().getProject();
×
NEW
871
            props.setAll(context.getModel().getEnvironment().getProperties());
×
NEW
872
            props.setAll(context.getModel().getEnvironment().getSourcedProperties());
×
873
            props.set(Constants.KEY_PROJECT_NAME, project.getName());
×
874
            props.set(Constants.KEY_PROJECT_NAME_CAPITALIZED, getCapitalizedName(project.getName()));
×
875
            props.set(Constants.KEY_PROJECT_STEREOTYPE, project.getStereotype());
×
876
            props.set(Constants.KEY_PROJECT_VERSION, project.getVersion());
×
877
            props.set(Constants.KEY_PROJECT_SNAPSHOT, String.valueOf(project.isSnapshot()));
×
878
            if (isNotBlank(project.getDescription())) {
×
879
                props.set(Constants.KEY_PROJECT_DESCRIPTION, MustacheUtils.passThrough(project.getDescription()));
×
880
            }
881
            if (isNotBlank(project.getLongDescription())) {
×
882
                props.set(Constants.KEY_PROJECT_LONG_DESCRIPTION, MustacheUtils.passThrough(project.getLongDescription()));
×
883
            }
884
            props.set(Constants.KEY_PROJECT_LICENSE, project.getLicense());
×
885
            props.set(Constants.KEY_PROJECT_INCEPTION_YEAR, project.getInceptionYear());
×
886
            props.set(Constants.KEY_PROJECT_COPYRIGHT, project.getCopyright());
×
887
            props.set(Constants.KEY_PROJECT_VENDOR, project.getVendor());
×
888
            project.getLinks().fillProps(props);
×
889

890
            project.getLanguages().fillProperties(props);
×
891

892
            project.parseVersion();
×
893
            props.setAll(project.resolvedExtraProperties());
×
894

895
            String osName = PlatformUtils.getDetectedOs();
×
896
            String osArch = PlatformUtils.getDetectedArch();
×
897
            props.set(Constants.KEY_OS_NAME, osName);
×
898
            props.set(Constants.KEY_OS_ARCH, osArch);
×
899
            props.set(Constants.KEY_OS_VERSION, PlatformUtils.getDetectedVersion());
×
900
            props.set(Constants.KEY_OS_PLATFORM, PlatformUtils.getCurrentFull());
×
NEW
901
            props.set(Constants.KEY_OS_PLATFORM_REPLACED, context.getModel().getPlatform().applyReplacements(PlatformUtils.getCurrentFull()));
×
902

NEW
903
            applyTemplates(context.getLogger(), props, project.resolvedExtraProperties());
×
NEW
904
            props.set(Constants.KEY_ZONED_DATE_TIME_NOW, context.getModel().getNow());
×
905

906
            return props;
×
907
        }
908
    }
909

910
    public static class Links extends AbstractModelObject<Links> implements Domain {
1✔
911
        private static final long serialVersionUID = 6565958050416900198L;
912
        private static final String PROJECT_LINK = "projectLink";
913

914
        private String homepage;
915
        private String documentation;
916
        private String license;
917
        private String bugTracker;
918
        private String faq;
919
        private String help;
920
        private String donation;
921
        private String translate;
922
        private String contact;
923
        private String vcsBrowser;
924
        private String contribute;
925

926
        @JsonIgnore
1✔
927
        private final org.jreleaser.model.api.project.Project.Links immutable = new org.jreleaser.model.api.project.Project.Links() {
1✔
928
            private static final long serialVersionUID = 3891594676066031996L;
929

930
            @Override
931
            public String getHomepage() {
932
                return homepage;
×
933
            }
934

935
            @Override
936
            public String getDocumentation() {
937
                return documentation;
×
938
            }
939

940
            @Override
941
            public String getLicense() {
942
                return license;
×
943
            }
944

945
            @Override
946
            public String getBugTracker() {
947
                return bugTracker;
×
948
            }
949

950
            @Override
951
            public String getFaq() {
952
                return faq;
×
953
            }
954

955
            @Override
956
            public String getHelp() {
957
                return help;
×
958
            }
959

960
            @Override
961
            public String getDonation() {
962
                return donation;
×
963
            }
964

965
            @Override
966
            public String getTranslate() {
967
                return translate;
×
968
            }
969

970
            @Override
971
            public String getContact() {
972
                return contact;
×
973
            }
974

975
            @Override
976
            public String getVcsBrowser() {
977
                return vcsBrowser;
×
978
            }
979

980
            @Override
981
            public String getContribute() {
982
                return contribute;
×
983
            }
984

985
            @Override
986
            public Map<String, Object> asMap(boolean full) {
987
                return unmodifiableMap(Links.this.asMap(full));
×
988
            }
989
        };
990

991
        public org.jreleaser.model.api.project.Project.Links asImmutable() {
992
            return immutable;
×
993
        }
994

995
        @Override
996
        public void merge(Links source) {
997
            this.homepage = merge(this.homepage, source.homepage);
1✔
998
            this.documentation = merge(this.documentation, source.documentation);
1✔
999
            this.license = merge(this.license, source.license);
1✔
1000
            this.bugTracker = merge(this.bugTracker, source.bugTracker);
1✔
1001
            this.faq = merge(this.faq, source.faq);
1✔
1002
            this.help = merge(this.help, source.help);
1✔
1003
            this.donation = merge(this.donation, source.donation);
1✔
1004
            this.translate = merge(this.translate, source.translate);
1✔
1005
            this.contact = merge(this.contact, source.contact);
1✔
1006
            this.vcsBrowser = merge(this.vcsBrowser, source.vcsBrowser);
1✔
1007
            this.contribute = merge(this.contribute, source.contribute);
1✔
1008
        }
1✔
1009

1010
        public String getHomepage() {
1011
            return homepage;
1✔
1012
        }
1013

1014
        public void setHomepage(String homepage) {
1015
            this.homepage = homepage;
1✔
1016
        }
1✔
1017

1018
        public String getDocumentation() {
1019
            return documentation;
1✔
1020
        }
1021

1022
        public void setDocumentation(String documentation) {
1023
            this.documentation = documentation;
1✔
1024
        }
1✔
1025

1026
        public String getLicense() {
1027
            return license;
1✔
1028
        }
1029

1030
        public void setLicense(String license) {
1031
            this.license = license;
1✔
1032
        }
1✔
1033

1034
        public String getBugTracker() {
1035
            return bugTracker;
1✔
1036
        }
1037

1038
        public void setBugTracker(String bugTracker) {
1039
            this.bugTracker = bugTracker;
1✔
1040
        }
1✔
1041

1042
        public String getFaq() {
1043
            return faq;
×
1044
        }
1045

1046
        public void setFaq(String faq) {
1047
            this.faq = faq;
×
1048
        }
×
1049

1050
        public String getHelp() {
1051
            return help;
×
1052
        }
1053

1054
        public void setHelp(String help) {
1055
            this.help = help;
×
1056
        }
×
1057

1058
        public String getDonation() {
1059
            return donation;
×
1060
        }
1061

1062
        public void setDonation(String donation) {
1063
            this.donation = donation;
×
1064
        }
×
1065

1066
        public String getTranslate() {
1067
            return translate;
×
1068
        }
1069

1070
        public void setTranslate(String translate) {
1071
            this.translate = translate;
×
1072
        }
×
1073

1074
        public String getContact() {
1075
            return contact;
×
1076
        }
1077

1078
        public void setContact(String contact) {
1079
            this.contact = contact;
×
1080
        }
×
1081

1082
        public String getVcsBrowser() {
1083
            return vcsBrowser;
1✔
1084
        }
1085

1086
        public void setVcsBrowser(String vcsBrowser) {
1087
            this.vcsBrowser = vcsBrowser;
1✔
1088
        }
1✔
1089

1090
        public String getContribute() {
1091
            return contribute;
×
1092
        }
1093

1094
        public void setContribute(String contribute) {
1095
            this.contribute = contribute;
×
1096
        }
×
1097

1098
        @Override
1099
        public Map<String, Object> asMap(boolean full) {
1100
            Map<String, Object> map = new LinkedHashMap<>();
1✔
1101
            if (isNotBlank(homepage)) map.put("homepage", homepage);
1✔
1102
            if (isNotBlank(documentation)) map.put("documentation", documentation);
1✔
1103
            if (isNotBlank(license)) map.put("license", license);
1✔
1104
            if (isNotBlank(bugTracker)) map.put("bugTracker", bugTracker);
1✔
1105
            if (isNotBlank(vcsBrowser)) map.put("vcsBrowser", vcsBrowser);
1✔
1106
            if (isNotBlank(faq)) map.put("faq", faq);
1✔
1107
            if (isNotBlank(help)) map.put("help", help);
1✔
1108
            if (isNotBlank(donation)) map.put("donation", donation);
1✔
1109
            if (isNotBlank(translate)) map.put("translate", translate);
1✔
1110
            if (isNotBlank(contact)) map.put("contact", contact);
1✔
1111
            if (isNotBlank(contribute)) map.put("contribute", contribute);
1✔
1112
            return map;
1✔
1113
        }
1114

1115
        public void fillProps(TemplateContext props) {
1116
            props.set(PROJECT_LINK + "Homepage", homepage);
1✔
1117
            props.set(PROJECT_LINK + "Documentation", documentation);
1✔
1118
            props.set(PROJECT_LINK + "License", license);
1✔
1119
            props.set(PROJECT_LINK + "BugTracker", bugTracker);
1✔
1120
            props.set(PROJECT_LINK + "VcsBrowser", vcsBrowser);
1✔
1121
            props.set(PROJECT_LINK + "Faq", faq);
1✔
1122
            props.set(PROJECT_LINK + "Help", help);
1✔
1123
            props.set(PROJECT_LINK + "Donation", donation);
1✔
1124
            props.set(PROJECT_LINK + "Translate", translate);
1✔
1125
            props.set(PROJECT_LINK + "Contact", contact);
1✔
1126
            props.set(PROJECT_LINK + "Contribute", contribute);
1✔
1127
            // TODO: Remove these in 2.0.0
1128
            props.set(Constants.KEY_PROJECT_WEBSITE, homepage);
1✔
1129
            props.set(Constants.KEY_PROJECT_DOCS_URL, documentation);
1✔
1130
            props.set(Constants.KEY_PROJECT_LICENSE_URL, license);
1✔
1131
        }
1✔
1132

1133
        public Collection<LinkTemplate> asLinkTemplates(boolean flatpak) {
1134
            List<LinkTemplate> links = new ArrayList<>();
1✔
1135
            if (isNotBlank(homepage)) links.add(new LinkTemplate("homepage", homepage));
1✔
1136
            if (!flatpak && isNotBlank(documentation)) links.add(new LinkTemplate("help", documentation));
1✔
1137
            if (isNotBlank(bugTracker)) links.add(new LinkTemplate("bugtracker", bugTracker));
1✔
1138
            if (isNotBlank(vcsBrowser)) links.add(new LinkTemplate("vcs-browser", vcsBrowser));
1✔
1139
            if (isNotBlank(faq)) links.add(new LinkTemplate("faq", faq));
1✔
1140
            if (isNotBlank(help)) links.add(new LinkTemplate("help", help));
1✔
1141
            if (isNotBlank(donation)) links.add(new LinkTemplate("donation", donation));
1✔
1142
            if (isNotBlank(translate)) links.add(new LinkTemplate("translate", translate));
1✔
1143
            if (isNotBlank(contact)) links.add(new LinkTemplate("contact", contact));
1✔
1144
            if (isNotBlank(contribute)) links.add(new LinkTemplate("contribute", contribute));
1✔
1145
            return links;
1✔
1146
        }
1147

1148
        public static final class LinkTemplate {
1149
            private final String type;
1150
            private final String url;
1151

1152
            public LinkTemplate(String type, String url) {
1✔
1153
                this.type = type;
1✔
1154
                this.url = url;
1✔
1155
            }
1✔
1156

1157
            public String getType() {
1158
                return type;
1✔
1159
            }
1160

1161
            public TemplateFunction getUrl() {
1162
                return s -> url;
1✔
1163
            }
1164
        }
1165
    }
1166

1167
    public static class VersionPattern extends AbstractModelObject<VersionPattern> implements Serializable {
1168
        private static final long serialVersionUID = -8292733451111227968L;
1169

1170
        private org.jreleaser.model.VersionPattern.Type type;
1171
        private String format;
1172

1173
        @JsonIgnore
1✔
1174
        private final org.jreleaser.model.api.project.Project.VersionPattern immutable = new org.jreleaser.model.api.project.Project.VersionPattern() {
1✔
1175
            private static final long serialVersionUID = 1073045324421554619L;
1176

1177
            @Override
1178
            public org.jreleaser.model.VersionPattern.Type getType() {
1179
                return type;
×
1180
            }
1181

1182
            @Override
1183
            public String getFormat() {
1184
                return format;
×
1185
            }
1186

1187
            @Override
1188
            public String toString() {
1189
                return VersionPattern.this.toString();
×
1190
            }
1191
        };
1192

1193
        public VersionPattern() {
1✔
1194
            this.type = org.jreleaser.model.VersionPattern.Type.SEMVER;
1✔
1195
        }
1✔
1196

1197
        public org.jreleaser.model.api.project.Project.VersionPattern asImmutable() {
1198
            return immutable;
×
1199
        }
1200

1201
        public org.jreleaser.model.VersionPattern.Type getType() {
1202
            return type;
1✔
1203
        }
1204

1205
        public void setType(org.jreleaser.model.VersionPattern.Type type) {
1206
            this.type = type;
1✔
1207
        }
1✔
1208

1209
        public String getFormat() {
1210
            return format;
×
1211
        }
1212

1213
        public void setFormat(String format) {
1214
            this.format = format;
×
1215
        }
×
1216

1217
        @Override
1218
        public String toString() {
1219
            String s = type.toString();
1✔
1220
            switch (type) {
1✔
1221
                case CALVER:
1222
                case CUSTOM:
1223
                    if (isNotBlank(format)) {
×
1224
                        s += ":" + format;
×
1225
                    }
1226
                    break;
1227
                default:
1228
                    // noop
1229
                    break;
1230
            }
1231
            return s;
1✔
1232
        }
1233

1234
        @Override
1235
        public void merge(VersionPattern source) {
1236
            if (null != source) {
1✔
1237
                this.type = merge(this.type, source.type);
1✔
1238
                this.format = merge(this.format, source.format);
1✔
1239
            }
1240
        }
1✔
1241

1242
        public static VersionPattern of(String str) {
1243
            if (isBlank(str)) return null;
1✔
1244

1245
            String[] parts = str.trim().split(":");
1✔
1246

1247
            VersionPattern vp = new VersionPattern();
1✔
1248
            switch (parts.length) {
1✔
1249
                case 1:
1250
                    vp.setType(org.jreleaser.model.VersionPattern.Type.of(parts[0]));
1✔
1251
                    break;
1✔
1252
                case 2:
1253
                    vp.setType(org.jreleaser.model.VersionPattern.Type.of(parts[0]));
×
1254
                    vp.setFormat(parts[1].trim());
×
1255
                    break;
×
1256
                default:
1257
                    throw new IllegalArgumentException();
×
1258
            }
1259

1260
            return vp;
1✔
1261
        }
1262
    }
1263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc