001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.csv;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.ThrowableUtils.*;
022import static org.apache.juneau.commons.utils.Utils.*;
023
024import java.io.*;
025import java.lang.reflect.*;
026import java.nio.charset.*;
027import java.util.*;
028import java.util.function.*;
029
030import org.apache.juneau.*;
031import org.apache.juneau.commons.lang.*;
032import org.apache.juneau.httppart.*;
033import org.apache.juneau.serializer.*;
034import org.apache.juneau.svl.*;
035
036/**
037 * Session object that lives for the duration of a single use of {@link CsvSerializer}.
038 *
039 * <h5 class='section'>Notes:</h5><ul>
040 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
041 * </ul>
042 *
043 */
044@SuppressWarnings("resource")
045public class CsvSerializerSession extends WriterSerializerSession {
046   /**
047    * Builder class.
048    */
049   public static class Builder extends WriterSerializerSession.Builder {
050
051      /**
052       * Constructor
053       *
054       * @param ctx The context creating this session.
055       *    <br>Cannot be <jk>null</jk>.
056       */
057      protected Builder(CsvSerializer ctx) {
058         super(assertArgNotNull("ctx", ctx));
059      }
060
061      @Override /* Overridden from Builder */
062      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
063         super.apply(type, apply);
064         return this;
065      }
066
067      @Override
068      public CsvSerializerSession build() {
069         return new CsvSerializerSession(this);
070      }
071
072      @Override /* Overridden from Builder */
073      public Builder debug(Boolean value) {
074         super.debug(value);
075         return this;
076      }
077
078      @Override /* Overridden from Builder */
079      public Builder fileCharset(Charset value) {
080         super.fileCharset(value);
081         return this;
082      }
083
084      @Override /* Overridden from Builder */
085      public Builder javaMethod(Method value) {
086         super.javaMethod(value);
087         return this;
088      }
089
090      @Override /* Overridden from Builder */
091      public Builder locale(Locale value) {
092         super.locale(value);
093         return this;
094      }
095
096      @Override /* Overridden from Builder */
097      public Builder mediaType(MediaType value) {
098         super.mediaType(value);
099         return this;
100      }
101
102      @Override /* Overridden from Builder */
103      public Builder mediaTypeDefault(MediaType value) {
104         super.mediaTypeDefault(value);
105         return this;
106      }
107
108      @Override /* Overridden from Builder */
109      public Builder properties(Map<String,Object> value) {
110         super.properties(value);
111         return this;
112      }
113
114      @Override /* Overridden from Builder */
115      public Builder property(String key, Object value) {
116         super.property(key, value);
117         return this;
118      }
119
120      @Override /* Overridden from Builder */
121      public Builder resolver(VarResolverSession value) {
122         super.resolver(value);
123         return this;
124      }
125
126      @Override /* Overridden from Builder */
127      public Builder schema(HttpPartSchema value) {
128         super.schema(value);
129         return this;
130      }
131
132      @Override /* Overridden from Builder */
133      public Builder schemaDefault(HttpPartSchema value) {
134         super.schemaDefault(value);
135         return this;
136      }
137
138      @Override /* Overridden from Builder */
139      public Builder streamCharset(Charset value) {
140         super.streamCharset(value);
141         return this;
142      }
143
144      @Override /* Overridden from Builder */
145      public Builder timeZone(TimeZone value) {
146         super.timeZone(value);
147         return this;
148      }
149
150      @Override /* Overridden from Builder */
151      public Builder timeZoneDefault(TimeZone value) {
152         super.timeZoneDefault(value);
153         return this;
154      }
155
156      @Override /* Overridden from Builder */
157      public Builder unmodifiable() {
158         super.unmodifiable();
159         return this;
160      }
161
162      @Override /* Overridden from Builder */
163      public Builder uriContext(UriContext value) {
164         super.uriContext(value);
165         return this;
166      }
167
168      @Override /* Overridden from Builder */
169      public Builder useWhitespace(Boolean value) {
170         super.useWhitespace(value);
171         return this;
172      }
173   }
174
175   /**
176    * Creates a new builder for this object.
177    *
178    * @param ctx The context creating this session.
179    *    <br>Cannot be <jk>null</jk>.
180    * @return A new builder.
181    */
182   public static Builder create(CsvSerializer ctx) {
183      return new Builder(assertArgNotNull("ctx", ctx));
184   }
185
186   /**
187    * Constructor.
188    *
189    * @param builder The builder for this object.
190    */
191   protected CsvSerializerSession(Builder builder) {
192      super(builder);
193   }
194
195   /**
196    * Applies any registered object swap to the specified value.
197    *
198    * <p>
199    * If a swap is registered for the value's type, the value is transformed using the swap's
200    * {@code swap()} method before being serialized.
201    *
202    * @param value The value to potentially swap.
203    * @param type The class metadata of the value's type.
204    * @return The swapped value, or the original value if no swap is registered.
205    */
206   @SuppressWarnings({ "rawtypes" })
207   private Object applySwap(Object value, ClassMeta<?> type) {
208      try {
209         if (value == null || type == null)
210            return value;
211
212         org.apache.juneau.swap.ObjectSwap swap = type.getSwap(this);
213         if (nn(swap)) {
214            return swap(swap, value);
215         }
216         return value;
217      } catch (SerializeException e) {
218         throw rex(e);
219      }
220   }
221
222   @SuppressWarnings({ "rawtypes", "unchecked" })
223   @Override /* Overridden from SerializerSession */
224   protected void doSerialize(SerializerPipe pipe, Object o) throws IOException, SerializeException {
225
226      try (var w = getCsvWriter(pipe)) {
227         var cm = getClassMetaForObject(o);
228         var l = (Collection<?>)null;
229         if (cm.isArray()) {
230            l = l((Object[])o);
231         } else if (cm.isCollection()) {
232            l = (Collection<?>)o;
233         } else {
234            l = Collections.singleton(o);
235         }
236
237         // TODO - Doesn't support DynaBeans.
238         if (ne(l)) {
239            var entryType = getClassMetaForObject(l.iterator().next());
240            if (entryType.isBean()) {
241               var bm = entryType.getBeanMeta();
242               var addComma = Flag.create();
243
244               bm.getProperties().values().stream().filter(BeanPropertyMeta::canRead).forEach(x -> {
245                  addComma.ifSet(() -> w.w(',')).set();
246                  w.writeEntry(x.getName());
247               });
248
249//             bm.forEachProperty(BeanPropertyMeta::canRead, x -> {
250//                addComma.ifSet(() -> w.w(',')).set();
251//                w.writeEntry(x.getName());
252//             });
253               w.append('\n');
254               l.forEach(x -> {
255                  var addComma2 = Flag.create();
256                  BeanMap<?> bean = toBeanMap(x);
257                  bm.getProperties().values().stream().filter(BeanPropertyMeta::canRead).forEach(y -> {
258                     addComma2.ifSet(() -> w.w(',')).set();
259                     // Bean property values are already swapped by BeanPropertyMeta.get() via toSerializedForm()
260                     var value = y.get(bean, y.getName());
261                     w.writeEntry(value);
262                  });
263                  w.w('\n');
264               });
265            } else if (entryType.isMap()) {
266               var addComma = Flag.create();
267               var first = (Map)l.iterator().next();
268               first.keySet().forEach(x -> {
269                  addComma.ifSet(() -> w.w(',')).set();
270                  w.writeEntry(x);
271               });
272               w.append('\n');
273               l.stream().forEach(x -> {
274                  var addComma2 = Flag.create();
275                  var map = (Map)x;
276                  map.values().forEach(y -> {
277                     addComma2.ifSet(() -> w.w(',')).set();
278                     var value = applySwap(y, getClassMetaForObject(y));
279                     w.writeEntry(value);
280                  });
281                  w.w('\n');
282               });
283            } else {
284               w.writeEntry("value");
285               w.append('\n');
286               l.stream().forEach(x -> {
287                  var value = applySwap(x, getClassMetaForObject(x));
288                  w.writeEntry(value);
289                  w.w('\n');
290               });
291            }
292         }
293      }
294   }
295
296   CsvWriter getCsvWriter(SerializerPipe out) {
297      var output = out.getRawOutput();
298      if (output instanceof CsvWriter output2)
299         return output2;
300      var w = new CsvWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), getQuoteChar(), isTrimStrings(), getUriResolver());
301      out.setWriter(w);
302      return w;
303   }
304}