001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.pyranid; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.NotThreadSafe; 023import javax.annotation.concurrent.ThreadSafe; 024import java.time.ZoneId; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.List; 029import java.util.Objects; 030import java.util.Optional; 031import java.util.Queue; 032import java.util.concurrent.ConcurrentLinkedQueue; 033import java.util.stream.Collectors; 034 035import static java.lang.String.format; 036import static java.util.Objects.requireNonNull; 037 038/** 039 * Data that represents a SQL statement. 040 * 041 * @author <a href="https://www.revetkn.com">Mark Allen</a> 042 * @since 2.0.0 043 */ 044@ThreadSafe 045public final class StatementContext<T> { 046 @NonNull 047 private final Statement statement; 048 @NonNull 049 private final List<@Nullable Object> parameters; 050 @Nullable 051 private final Class<T> resultSetRowType; 052 @NonNull 053 private final DatabaseType databaseType; 054 @NonNull 055 private final ZoneId timeZone; 056 @NonNull 057 private final Queue<@NonNull AutoCloseable> cleanupOperations; 058 059 protected StatementContext(@NonNull Builder builder) { 060 requireNonNull(builder); 061 062 this.statement = builder.statement; 063 this.parameters = builder.parameters == null 064 ? List.of() 065 : Collections.unmodifiableList(new ArrayList<>(builder.parameters)); 066 this.resultSetRowType = builder.resultSetRowType; 067 this.databaseType = builder.databaseType; 068 this.timeZone = builder.timeZone; 069 this.cleanupOperations = new ConcurrentLinkedQueue<>(); 070 } 071 072 @Override 073 public int hashCode() { 074 return Objects.hash(getStatement(), getParameters(), getResultSetRowType(), getDatabaseType(), getTimeZone()); 075 } 076 077 @Override 078 public boolean equals(Object object) { 079 if (this == object) 080 return true; 081 082 if (!(object instanceof StatementContext)) 083 return false; 084 085 StatementContext statementContext = (StatementContext) object; 086 087 return Objects.equals(statementContext.getStatement(), getStatement()) 088 && Objects.equals(statementContext.getParameters(), getParameters()) 089 && Objects.equals(statementContext.getResultSetRowType(), getResultSetRowType()) 090 && Objects.equals(statementContext.getDatabaseType(), getDatabaseType()) 091 && Objects.equals(statementContext.getTimeZone(), getTimeZone()); 092 } 093 094 @Override 095 public String toString() { 096 List<String> components = new ArrayList<>(3); 097 098 components.add(format("statement=%s", getStatement())); 099 100 if (getParameters().size() > 0) 101 components.add(format("parameters=%s", getParameters())); 102 103 Class<T> resultSetRowType = getResultSetRowType().orElse(null); 104 105 if (resultSetRowType != null) 106 components.add(format("resultSetRowType=%s", resultSetRowType)); 107 108 components.add(format("databaseType=%s", getDatabaseType().name())); 109 components.add(format("timeZone=%s", getTimeZone().getId())); 110 111 return format("%s{%s}", getClass().getSimpleName(), components.stream().collect(Collectors.joining(", "))); 112 } 113 114 @NonNull 115 public Statement getStatement() { 116 return this.statement; 117 } 118 119 @NonNull 120 public List<@Nullable Object> getParameters() { 121 return this.parameters; 122 } 123 124 @NonNull 125 public Optional<Class<T>> getResultSetRowType() { 126 return Optional.ofNullable(this.resultSetRowType); 127 } 128 129 @NonNull 130 public DatabaseType getDatabaseType() { 131 return this.databaseType; 132 } 133 134 @NonNull 135 public ZoneId getTimeZone() { 136 return this.timeZone; 137 } 138 139 void addCleanupOperation(@NonNull AutoCloseable cleanupOperation) { 140 requireNonNull(cleanupOperation); 141 this.cleanupOperations.add(cleanupOperation); 142 } 143 144 @NonNull 145 Queue<@NonNull AutoCloseable> getCleanupOperations() { 146 return this.cleanupOperations; 147 } 148 149 @NonNull 150 public static <T> Builder<T> with(@NonNull Statement statement, 151 @NonNull Database database) { 152 requireNonNull(statement); 153 requireNonNull(database); 154 155 return new Builder<>(statement, database); 156 } 157 158 /** 159 * Builder used to construct instances of {@link StatementContext}. 160 * <p> 161 * This class is intended for use by a single thread. 162 * 163 * @author <a href="https://www.revetkn.com">Mark Allen</a> 164 * @since 2.0.0 165 */ 166 @NotThreadSafe 167 public static class Builder<T> { 168 @NonNull 169 private final Statement statement; 170 @NonNull 171 private final DatabaseType databaseType; 172 @NonNull 173 private final ZoneId timeZone; 174 @Nullable 175 private List<@Nullable Object> parameters; 176 @Nullable 177 private Class<T> resultSetRowType; 178 179 private Builder(@NonNull Statement statement, 180 @NonNull Database database) { 181 requireNonNull(statement); 182 requireNonNull(database); 183 184 this.statement = statement; 185 this.databaseType = database.getDatabaseType(); 186 this.timeZone = database.getTimeZone(); 187 } 188 189 @NonNull 190 public Builder parameters(@Nullable List<@Nullable Object> parameters) { 191 this.parameters = parameters; 192 return this; 193 } 194 195 @NonNull 196 public Builder parameters(Object @Nullable ... parameters) { 197 this.parameters = parameters == null ? null : Arrays.asList(parameters); 198 return this; 199 } 200 201 @NonNull 202 public Builder resultSetRowType(Class<T> resultSetRowType) { 203 this.resultSetRowType = resultSetRowType; 204 return this; 205 } 206 207 @NonNull 208 public StatementContext build() { 209 return new StatementContext<>(this); 210 } 211 } 212}