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}