• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 2604

03 Jan 2025 10:00AM UTC coverage: 87.524% (+0.3%) from 87.177%
2604

Pull #3146

github

web-flow
Merge 60c1f5fea into 8ac3920af
Pull Request #3146: Shared ambiguity instance

3633 of 4401 branches covered (82.55%)

4 of 4 new or added lines in 1 file covered. (100.0%)

254 existing lines in 22 files now uncovered.

9569 of 10933 relevant lines covered (87.52%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.89
/src/main/java/org/apache/ibatis/scripting/xmltags/ForEachSqlNode.java
1
/*
2
 *    Copyright 2009-2025 the original author or authors.
3
 *
4
 *    Licensed under the Apache License, Version 2.0 (the "License");
5
 *    you may not use this file except in compliance with the License.
6
 *    You may obtain a copy of the License at
7
 *
8
 *       https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *    Unless required by applicable law or agreed to in writing, software
11
 *    distributed under the License is distributed on an "AS IS" BASIS,
12
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *    See the License for the specific language governing permissions and
14
 *    limitations under the License.
15
 */
16
package org.apache.ibatis.scripting.xmltags;
17

18
import java.util.Map;
19
import java.util.Optional;
20

21
import org.apache.ibatis.parsing.GenericTokenParser;
22
import org.apache.ibatis.session.Configuration;
23

24
/**
25
 * @author Clinton Begin
26
 */
27
public class ForEachSqlNode implements SqlNode {
28
  public static final String ITEM_PREFIX = "__frch_";
29

30
  private final ExpressionEvaluator evaluator = ExpressionEvaluator.INSTANCE;
1✔
31
  private final String collectionExpression;
32
  private final Boolean nullable;
33
  private final SqlNode contents;
34
  private final String open;
35
  private final String close;
36
  private final String separator;
37
  private final String item;
38
  private final String index;
39
  private final Configuration configuration;
40

41
  /**
42
   * @deprecated Since 3.5.9, use the
43
   *             {@link #ForEachSqlNode(Configuration, SqlNode, String, Boolean, String, String, String, String, String)}.
44
   */
45
  @Deprecated
46
  public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index,
47
      String item, String open, String close, String separator) {
48
    this(configuration, contents, collectionExpression, null, index, item, open, close, separator);
1✔
49
  }
1✔
50

51
  /**
52
   * @since 3.5.9
53
   */
54
  public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, Boolean nullable,
55
      String index, String item, String open, String close, String separator) {
1✔
56
    this.collectionExpression = collectionExpression;
1✔
57
    this.nullable = nullable;
1✔
58
    this.contents = contents;
1✔
59
    this.open = open;
1✔
60
    this.close = close;
1✔
61
    this.separator = separator;
1✔
62
    this.index = index;
1✔
63
    this.item = item;
1✔
64
    this.configuration = configuration;
1✔
65
  }
1✔
66

67
  @Override
68
  public boolean apply(DynamicContext context) {
69
    Map<String, Object> bindings = context.getBindings();
1✔
70
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings,
1✔
71
        Optional.ofNullable(nullable).orElseGet(configuration::isNullableOnForEach));
1✔
72
    if (iterable == null || !iterable.iterator().hasNext()) {
1✔
73
      return true;
1✔
74
    }
75
    boolean first = true;
1✔
76
    applyOpen(context);
1✔
77
    int i = 0;
1✔
78
    for (Object o : iterable) {
1✔
79
      DynamicContext oldContext = context;
1✔
80
      if (first || separator == null) {
1✔
81
        context = new PrefixedContext(context, "");
1✔
82
      } else {
83
        context = new PrefixedContext(context, separator);
1✔
84
      }
85
      int uniqueNumber = context.getUniqueNumber();
1✔
86
      // Issue #709
87
      if (o instanceof Map.Entry) {
1✔
88
        @SuppressWarnings("unchecked")
89
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
1✔
90
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
1✔
91
        applyItem(context, mapEntry.getValue(), uniqueNumber);
1✔
92
      } else {
1✔
93
        applyIndex(context, i, uniqueNumber);
1✔
94
        applyItem(context, o, uniqueNumber);
1✔
95
      }
96
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
1✔
97
      if (first) {
1✔
98
        first = !((PrefixedContext) context).isPrefixApplied();
1✔
99
      }
100
      context = oldContext;
1✔
101
      i++;
1✔
102
    }
1✔
103
    applyClose(context);
1✔
104
    context.getBindings().remove(item);
1✔
105
    context.getBindings().remove(index);
1✔
106
    return true;
1✔
107
  }
108

109
  private void applyIndex(DynamicContext context, Object o, int i) {
110
    if (index != null) {
1✔
111
      context.bind(index, o);
1✔
112
      context.bind(itemizeItem(index, i), o);
1✔
113
    }
114
  }
1✔
115

116
  private void applyItem(DynamicContext context, Object o, int i) {
117
    if (item != null) {
1✔
118
      context.bind(item, o);
1✔
119
      context.bind(itemizeItem(item, i), o);
1✔
120
    }
121
  }
1✔
122

123
  private void applyOpen(DynamicContext context) {
124
    if (open != null) {
1✔
125
      context.appendSql(open);
1✔
126
    }
127
  }
1✔
128

129
  private void applyClose(DynamicContext context) {
130
    if (close != null) {
1✔
131
      context.appendSql(close);
1✔
132
    }
133
  }
1✔
134

135
  private static String itemizeItem(String item, int i) {
136
    return ITEM_PREFIX + item + "_" + i;
1✔
137
  }
138

139
  private static class FilteredDynamicContext extends DynamicContext {
140
    private final DynamicContext delegate;
141
    private final int index;
142
    private final String itemIndex;
143
    private final String item;
144

145
    public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex, String item,
146
        int i) {
147
      super(configuration, null);
1✔
148
      this.delegate = delegate;
1✔
149
      this.index = i;
1✔
150
      this.itemIndex = itemIndex;
1✔
151
      this.item = item;
1✔
152
    }
1✔
153

154
    @Override
155
    public Map<String, Object> getBindings() {
156
      return delegate.getBindings();
1✔
157
    }
158

159
    @Override
160
    public void bind(String name, Object value) {
161
      delegate.bind(name, value);
1✔
162
    }
1✔
163

164
    @Override
165
    public String getSql() {
UNCOV
166
      return delegate.getSql();
×
167
    }
168

169
    @Override
170
    public void appendSql(String sql) {
171
      GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
1✔
172
        String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
1✔
173
        if (itemIndex != null && newContent.equals(content)) {
1✔
174
          newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
1✔
175
        }
176
        return "#{" + newContent + "}";
1✔
177
      });
178

179
      delegate.appendSql(parser.parse(sql));
1✔
180
    }
1✔
181

182
    @Override
183
    public int getUniqueNumber() {
184
      return delegate.getUniqueNumber();
1✔
185
    }
186

187
  }
188

189
  private class PrefixedContext extends DynamicContext {
190
    private final DynamicContext delegate;
191
    private final String prefix;
192
    private boolean prefixApplied;
193

194
    public PrefixedContext(DynamicContext delegate, String prefix) {
1✔
195
      super(configuration, null);
1✔
196
      this.delegate = delegate;
1✔
197
      this.prefix = prefix;
1✔
198
      this.prefixApplied = false;
1✔
199
    }
1✔
200

201
    public boolean isPrefixApplied() {
202
      return prefixApplied;
1✔
203
    }
204

205
    @Override
206
    public Map<String, Object> getBindings() {
207
      return delegate.getBindings();
1✔
208
    }
209

210
    @Override
211
    public void bind(String name, Object value) {
212
      delegate.bind(name, value);
1✔
213
    }
1✔
214

215
    @Override
216
    public void appendSql(String sql) {
217
      if (!prefixApplied && sql != null && sql.trim().length() > 0) {
1!
218
        delegate.appendSql(prefix);
1✔
219
        prefixApplied = true;
1✔
220
      }
221
      delegate.appendSql(sql);
1✔
222
    }
1✔
223

224
    @Override
225
    public String getSql() {
UNCOV
226
      return delegate.getSql();
×
227
    }
228

229
    @Override
230
    public int getUniqueNumber() {
231
      return delegate.getUniqueNumber();
1✔
232
    }
233
  }
234

235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc