001package gudusoft.gsqlparser.ir.semantic; 002 003import java.util.Objects; 004 005/** 006 * A secondary source location that helps explain a {@link Diagnostic}. 007 * 008 * <p>{@code role} is a stable, non-localized machine value such as 009 * {@code first-marker}; {@code message} is user-visible explanatory text. 010 * 011 * <p><b>API status:</b> read-only consumption of analyzer-produced related 012 * locations is part of Join Analysis Consumption Profile v1. Constructors and 013 * factories are producer-oriented and are outside that profile. 014 */ 015public final class RelatedLocation { 016 017 private final String role; 018 private final String message; 019 private final SourceSpan span; 020 021 public RelatedLocation(String role, String message, SourceSpan span) { 022 this.role = requireNonEmpty(role, "role"); 023 this.message = requireNonEmpty(message, "message"); 024 this.span = Objects.requireNonNull(span, "span"); 025 } 026 027 public static RelatedLocation of(String role, String message, SourceSpan span) { 028 return new RelatedLocation(role, message, span); 029 } 030 031 public String getRole() { 032 return role; 033 } 034 035 public String getMessage() { 036 return message; 037 } 038 039 public SourceSpan getSpan() { 040 return span; 041 } 042 043 private static String requireNonEmpty(String value, String name) { 044 Objects.requireNonNull(value, name); 045 if (value.trim().isEmpty()) { 046 throw new IllegalArgumentException(name + " must not be empty"); 047 } 048 return value; 049 } 050 051 @Override 052 public boolean equals(Object o) { 053 if (this == o) return true; 054 if (!(o instanceof RelatedLocation)) return false; 055 RelatedLocation that = (RelatedLocation) o; 056 return role.equals(that.role) 057 && message.equals(that.message) 058 && span.equals(that.span); 059 } 060 061 @Override 062 public int hashCode() { 063 return Objects.hash(role, message, span); 064 } 065 066 @Override 067 public String toString() { 068 return role + ": " + message + " at " + span; 069 } 070}