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.function.Supplier; 034import java.util.stream.Collectors; 035 036import static java.lang.String.format; 037import static java.util.Objects.requireNonNull; 038 039/** 040 * Data that represents a SQL statement. 041 * 042 * @author <a href="https://www.revetkn.com">Mark Allen</a> 043 * @since 2.0.0 044 */ 045@ThreadSafe 046public final class StatementContext<T> { 047 @NonNull 048 private final Statement statement; 049 @NonNull 050 private final List<@Nullable Object> parameters; 051 @Nullable 052 private final Class<T> resultSetRowType; 053 @NonNull 054 private final Supplier<@NonNull DatabaseType> databaseTypeSupplier; 055 @NonNull 056 private final ZoneId timeZone; 057 @NonNull 058 private final Queue<@NonNull AutoCloseable> cleanupOperations; 059 060 protected StatementContext(@NonNull Builder builder) { 061 requireNonNull(builder); 062 063 this.statement = builder.statement; 064 this.parameters = builder.parameters == null 065 ? List.of() 066 : Collections.unmodifiableList(new ArrayList<>(builder.parameters)); 067 this.resultSetRowType = builder.resultSetRowType; 068 this.databaseTypeSupplier = builder.databaseTypeSupplier; 069 this.timeZone = builder.timeZone; 070 this.cleanupOperations = new ConcurrentLinkedQueue<>(); 071 } 072 073 @Override 074 public int hashCode() { 075 return Objects.hash(getStatement(), getParameters(), getResultSetRowType(), getDatabaseType(), getTimeZone()); 076 } 077 078 @Override 079 public boolean equals(Object object) { 080 if (this == object) 081 return true; 082 083 if (!(object instanceof StatementContext)) 084 return false; 085 086 StatementContext statementContext = (StatementContext) object; 087 088 return Objects.equals(statementContext.getStatement(), getStatement()) 089 && Objects.equals(statementContext.getParameters(), getParameters()) 090 && Objects.equals(statementContext.getResultSetRowType(), getResultSetRowType()) 091 && Objects.equals(statementContext.getDatabaseType(), getDatabaseType()) 092 && Objects.equals(statementContext.getTimeZone(), getTimeZone()); 093 } 094 095 @Override 096 public String toString() { 097 List<String> components = new ArrayList<>(3); 098 099 components.add(format("statement=%s", getStatement())); 100 101 if (getParameters().size() > 0) 102 components.add(format("parameters=%s", getParameters())); 103 104 Class<T> resultSetRowType = getResultSetRowType().orElse(null); 105 106 if (resultSetRowType != null) 107 components.add(format("resultSetRowType=%s", resultSetRowType)); 108 109 components.add(format("databaseType=%s", getDatabaseType().name())); 110 components.add(format("timeZone=%s", getTimeZone().getId())); 111 112 return format("%s{%s}", getClass().getSimpleName(), components.stream().collect(Collectors.joining(", "))); 113 } 114 115 @NonNull 116 public Statement getStatement() { 117 return this.statement; 118 } 119 120 @NonNull 121 public List<@Nullable Object> getParameters() { 122 return this.parameters; 123 } 124 125 @NonNull 126 public Optional<Class<T>> getResultSetRowType() { 127 return Optional.ofNullable(this.resultSetRowType); 128 } 129 130 @NonNull 131 public DatabaseType getDatabaseType() { 132 return this.databaseTypeSupplier.get(); 133 } 134 135 @NonNull 136 public ZoneId getTimeZone() { 137 return this.timeZone; 138 } 139 140 void addCleanupOperation(@NonNull AutoCloseable cleanupOperation) { 141 requireNonNull(cleanupOperation); 142 this.cleanupOperations.add(cleanupOperation); 143 } 144 145 @NonNull 146 Queue<@NonNull AutoCloseable> getCleanupOperations() { 147 return this.cleanupOperations; 148 } 149 150 @NonNull 151 public static <T> Builder<T> with(@NonNull Statement statement, 152 @NonNull Database database) { 153 requireNonNull(statement); 154 requireNonNull(database); 155 156 return new Builder<>(statement, database); 157 } 158 159 /** 160 * Builder used to construct instances of {@link StatementContext}. 161 * <p> 162 * This class is intended for use by a single thread. 163 * 164 * @author <a href="https://www.revetkn.com">Mark Allen</a> 165 * @since 2.0.0 166 */ 167 @NotThreadSafe 168 public static class Builder<T> { 169 @NonNull 170 private final Statement statement; 171 @NonNull 172 private final Supplier<@NonNull DatabaseType> databaseTypeSupplier; 173 @NonNull 174 private final ZoneId timeZone; 175 @Nullable 176 private List<@Nullable Object> parameters; 177 @Nullable 178 private Class<T> resultSetRowType; 179 180 private Builder(@NonNull Statement statement, 181 @NonNull Database database) { 182 requireNonNull(statement); 183 requireNonNull(database); 184 185 this.statement = statement; 186 this.databaseTypeSupplier = database::getDatabaseType; 187 this.timeZone = database.getTimeZone(); 188 } 189 190 @NonNull 191 public Builder parameters(@Nullable List<@Nullable Object> parameters) { 192 this.parameters = parameters; 193 return this; 194 } 195 196 @NonNull 197 public Builder parameters(Object @Nullable ... parameters) { 198 this.parameters = parameters == null ? null : Arrays.asList(parameters); 199 return this; 200 } 201 202 @NonNull 203 public Builder resultSetRowType(Class<T> resultSetRowType) { 204 this.resultSetRowType = resultSetRowType; 205 return this; 206 } 207 208 @NonNull 209 public StatementContext build() { 210 return new StatementContext<>(this); 211 } 212 } 213}