001/*
002 * JDrupes Builder
003 * Copyright (C) 2025, 2026 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.builder.api;
020
021import java.io.StringWriter;
022import java.util.Optional;
023
024/// Represents an exception that occurs during the build. Terminates the
025/// current build when thrown.
026///
027@SuppressWarnings("serial")
028public class BuildException extends RuntimeException {
029
030    /// The reason the build failed.
031    @SuppressWarnings("PMD.FieldNamingConventions")
032    public enum Reason {
033        /// A requested resource is unavailable, because transforming one
034        /// kind of resource into another kind of resource failed due
035        /// to an inconsistent state of the resources. It is up to the
036        /// provider to emit messages that describe the reason for
037        /// the failure. 
038        Unavailable,
039        
040        /// There is a problem with the build configuration. The exception's
041        /// message and optional details should provide more information.
042        Configuration,
043        
044        /// There is an unexpected problem with the build process, typically
045        /// due to an exception that cannot be handled and which should
046        /// be added as [#cause]. The [BuildException] will be reported to
047        /// the user with the stack trace.
048        Error
049    }
050
051    /// The reason.
052    private final Reason reason;
053    /// The message.
054    private String message;
055    /// The resource provider.
056    private ResourceProvider resourceProvider;
057    /// The details.
058    private final StringWriter details = new StringWriter();
059
060    /// Initializes a new build exception with its reason set to 
061    /// [BuildException.Reason#Error].
062    ///
063    public BuildException() {
064        reason = Reason.Error;
065    }
066
067    /// Initializes a new build exception with the given reason.
068    ///
069    /// @param reason the reason
070    ///
071    protected BuildException(Reason reason) {
072        this.reason = reason;
073    }
074    
075    /// Returns the reason why the build failed.
076    ///
077    /// @return the reason
078    ///
079    public Reason reason() {
080        return reason;
081    }
082    
083    /// Sets the message of the build exception. As a convenience, any
084    /// Throwable arguments will be replaced by their message.
085    ///
086    /// @param format the format
087    /// @param args the args
088    /// @return the build exception
089    ///
090    public BuildException message(String format, Object... args) {
091        for (int i = 0; i < args.length; i++) {
092            if (args[i] instanceof Throwable) {
093                args[i] = ((Throwable) args[i]).getMessage();
094            }
095        }
096        var message = String.format(format, args);
097        this.message = message;
098        return this;
099    }
100
101    @Override
102    public String getMessage() {
103        return message;
104    }
105
106    /// Sets the cause.
107    ///
108    /// @param cause the cause
109    /// @return the builds the exception
110    ///
111    public BuildException cause(Throwable cause) {
112        initCause(cause);
113        return this;
114    }
115
116    /// From.
117    ///
118    /// @param resourceProvider the resource provider
119    /// @return the builds the exception
120    ///
121    public BuildException from(ResourceProvider resourceProvider) {
122        this.resourceProvider = resourceProvider;
123        return this;
124    }
125
126    /// Add information to the details.
127    ///
128    /// @param detail the detail
129    /// @return the exception
130    ///
131    public BuildException detail(String detail) {
132        details.append(detail);
133        return this;
134    }
135
136    /// Returns the origin of this exception. This is the resource
137    /// provider set with [#from].
138    ///
139    /// @return the resource provider
140    ///
141    public Optional<ResourceProvider> origin() {
142        return Optional.ofNullable(resourceProvider);
143    }
144
145    /// Return the collected details.
146    ///
147    /// @return the string
148    ///
149    public String details() {
150        details.flush();
151        return details.toString();
152    }
153}