OSDN Git Service

Bug fix on XerEncoder.
[bm-asn1/bm-asn1.git] / jp / bitmeister / asn1 / codec / xer / XerEncoder.java
1 /*
2  * Copyright 2011 BitMeister Inc.
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  *     http://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 jp.bitmeister.asn1.codec.xer;
17
18 import static jp.bitmeister.asn1.codec.xer.XerConstants.BOOLEAN_FALSE;
19 import static jp.bitmeister.asn1.codec.xer.XerConstants.BOOLEAN_TRUE;
20 import static jp.bitmeister.asn1.codec.xer.XerConstants.END_TAG_START;
21 import static jp.bitmeister.asn1.codec.xer.XerConstants.REAL_MINUS_INFINITY;
22 import static jp.bitmeister.asn1.codec.xer.XerConstants.REAL_PLUS_INFINITY;
23 import static jp.bitmeister.asn1.codec.xer.XerConstants.REAL_ZERO;
24 import static jp.bitmeister.asn1.codec.xer.XerConstants.SINGLE_TAG_END;
25 import static jp.bitmeister.asn1.codec.xer.XerConstants.TAG_END;
26 import static jp.bitmeister.asn1.codec.xer.XerConstants.TAG_START;
27 import static jp.bitmeister.asn1.codec.xer.XerConstants.XML_PROLOG;
28
29 import java.io.OutputStream;
30 import java.io.UnsupportedEncodingException;
31 import java.math.BigDecimal;
32
33 import jp.bitmeister.asn1.codec.ASN1Encoder;
34 import jp.bitmeister.asn1.exception.ASN1EncodingException;
35 import jp.bitmeister.asn1.processor.ASN1Visitor;
36 import jp.bitmeister.asn1.type.ASN1Module;
37 import jp.bitmeister.asn1.type.ASN1Type;
38 import jp.bitmeister.asn1.type.CollectionType;
39 import jp.bitmeister.asn1.type.ConstructiveType;
40 import jp.bitmeister.asn1.type.ElementSpecification;
41 import jp.bitmeister.asn1.type.StringType;
42 import jp.bitmeister.asn1.type.TimeType;
43 import jp.bitmeister.asn1.type.UnknownType;
44 import jp.bitmeister.asn1.type.builtin.ANY;
45 import jp.bitmeister.asn1.type.builtin.BIT_STRING;
46 import jp.bitmeister.asn1.type.builtin.BOOLEAN;
47 import jp.bitmeister.asn1.type.builtin.CHOICE;
48 import jp.bitmeister.asn1.type.builtin.ENUMERATED;
49 import jp.bitmeister.asn1.type.builtin.INTEGER;
50 import jp.bitmeister.asn1.type.builtin.NULL;
51 import jp.bitmeister.asn1.type.builtin.OBJECT_IDENTIFIER;
52 import jp.bitmeister.asn1.type.builtin.OCTET_STRING;
53 import jp.bitmeister.asn1.type.builtin.REAL;
54 import jp.bitmeister.asn1.type.builtin.RELATIVE_OID;
55 import jp.bitmeister.asn1.type.builtin.SEQUENCE;
56 import jp.bitmeister.asn1.type.builtin.SEQUENCE_OF;
57 import jp.bitmeister.asn1.type.builtin.SET;
58 import jp.bitmeister.asn1.type.builtin.SET_OF;
59 import jp.bitmeister.asn1.value.BinString;
60 import jp.bitmeister.asn1.value.HexString;
61
62 /**
63  * XER (XML Encoding Rules) encoder.
64  * 
65  * <p>
66  * {@code XerEncoder} is an implementation of {@code ASN1Encoder}. It encodes an
67  * ASN.1 data to an XML document with XML Encoding Rules(XER) and writes the
68  * result to {@code OutputStream} that is specified when the encoder was
69  * instantiated. XER encoding process is light-weight compared with Canonical
70  * XML Encoding Rules (CXER) encoding because some restrictions on CXER are not
71  * considered.
72  * </p>
73  * 
74  * @author WATANABE, Jun. <jwat at bitmeister.jp>
75  * 
76  * @see ASN1Encoder
77  * @see XerDecoder
78  */
79 public class XerEncoder implements ASN1Encoder,
80                 ASN1Visitor<String, ASN1EncodingException> {
81
82         private Class<? extends ASN1Module> module;
83
84         private OutputStream out;
85
86         private StringBuilder builder;
87
88         private boolean prologIsEmpty = false;
89
90         /**
91          * Instantiates a {@code XEREncoder}.
92          * 
93          * @param out
94          *            The {@code OutputStream} that encoded octets will be written.
95          */
96         public XerEncoder(OutputStream out) {
97                 this.out = out;
98         }
99
100         /**
101          * Instantiates a {@code XEREncoder}.
102          * 
103          * @param module
104          *            The ASN.1 module used for encoding.
105          * @param out
106          *            The {@code OutputStream} that encoded octets will be written.
107          */
108         public XerEncoder(Class<? extends ASN1Module> module, OutputStream out) {
109                 this(out);
110                 this.module = module;
111         }
112
113         /**
114          * Instantiates a {@code XEREncoder}.
115          * 
116          * @param out
117          *            The {@code OutputStream} that encoded octets will be written.
118          * @param prologIsEmpty
119          *            If {@code true}, the XML prolog for result XML document will
120          *            be empty.
121          */
122         public XerEncoder(OutputStream out, boolean prologIsEmpty) {
123                 this(out);
124                 this.prologIsEmpty = prologIsEmpty;
125         }
126
127         /**
128          * Instantiates a {@code XEREncoder}.
129          * 
130          * @param module
131          *            The ASN.1 module used for encoding.
132          * @param out
133          *            The {@code OutputStream} that encoded octets will be written.
134          * @param prologIsEmpty
135          *            If {@code true}, the XML prolog for result XML document will
136          *            be empty.
137          */
138         public XerEncoder(Class<? extends ASN1Module> module, OutputStream out, boolean prologIsEmpty) {
139                 this(module, out);
140                 this.prologIsEmpty = prologIsEmpty;
141         }
142         
143         /**
144          * Encodes an ASN.1 data.
145          * 
146          * @param data
147          *            The ASN.1 data to be encoded.
148          * @return The size of encoded octets.
149          * @throws ASN1EncodingException
150          *             When an error occurred while the encoding process.
151          */
152         @Override
153         public int encode(ASN1Type data) throws ASN1EncodingException {
154                 if (module == null) {
155                         module = data.specification().module();
156                 }
157                 builder = new StringBuilder();
158                 if (!prologIsEmpty) {
159                         builder.append(XML_PROLOG);
160                 }
161                 encodeImpl(data, data.specification().xmlTypeName(module));
162                 try {
163                         byte[] result = builder.toString().getBytes("UTF-8");
164                         out.write(result);
165                         return result.length;
166                 } catch (Exception e) {
167                         ASN1EncodingException ex = new ASN1EncodingException();
168                         ex.setMessage("Failed to write result to stream.", e, null, null,
169                                         data);
170                         throw ex;
171                 }
172         }
173
174         /**
175          * Encodes the source data to xml documents and writes it to the
176          * {@code builder}.
177          * 
178          * @param data
179          *            The ASN.1 data to be encoded.
180          * @param xmlTagName
181          *            The xml tag name to be set.
182          * @throws ASN1EncodingException
183          *             When an error occurred while the encoding process.
184          */
185         private void encodeImpl(ASN1Type data, String xmlTagName)
186                         throws ASN1EncodingException {
187                 String contents = data.accept(this);
188                 if (contents == null) {
189                         return;
190                 } else if (contents.length() == 0) {
191                         builder.append(TAG_START).append(xmlTagName).append(SINGLE_TAG_END);
192                 } else {
193                         builder.append(TAG_START).append(xmlTagName).append(TAG_END);
194                         builder.append(contents);
195                         builder.append(END_TAG_START).append(xmlTagName).append(TAG_END);
196                 }
197         }
198
199         /*
200          * (non-Javadoc)
201          * 
202          * @see
203          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
204          * .builtin.BOOLEAN)
205          */
206         @Override
207         public String visit(BOOLEAN data) {
208                 return TAG_START + (data.value() ? BOOLEAN_TRUE : BOOLEAN_FALSE)
209                                 + SINGLE_TAG_END;
210         }
211
212         /*
213          * (non-Javadoc)
214          * 
215          * @see
216          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
217          * .builtin.INTEGER)
218          */
219         @Override
220         public String visit(INTEGER data) {
221                 String numberId = data.identifier();
222                 if (numberId != null) {
223                         return TAG_START + numberId + SINGLE_TAG_END;
224                 } else {
225                         return data.value().toString();
226                 }
227         }
228
229         /*
230          * (non-Javadoc)
231          * 
232          * @see
233          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
234          * .builtin.BIT_STRING)
235          */
236         @Override
237         public String visit(BIT_STRING data) {
238                 if (data.hasNamedBits()) {
239                         StringBuilder builder = new StringBuilder();
240                         for (int i = 0; i < data.size(); i++) {
241                                 if (data.bit(i)) {
242                                         String name = data.nameOfBit(i);
243                                         if (name != null) {
244                                                 builder.append(TAG_START).append(name)
245                                                                 .append(SINGLE_TAG_END);
246                                         }
247                                 }
248                         }
249                         return builder.toString();
250                 } else {
251                         return new BinString(data.value()).string();
252                 }
253         }
254
255         /*
256          * (non-Javadoc)
257          * 
258          * @see
259          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
260          * .builtin.OCTET_STRING)
261          */
262         @Override
263         public String visit(OCTET_STRING data) {
264                 return new HexString(data.value()).string();
265         }
266
267         /*
268          * (non-Javadoc)
269          * 
270          * @see
271          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
272          * .builtin.NULL)
273          */
274         @Override
275         public String visit(NULL data) {
276                 return "";
277         }
278
279         /*
280          * (non-Javadoc)
281          * 
282          * @see
283          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
284          * .builtin.OBJECT_IDENTIFIER)
285          */
286         @Override
287         public String visit(OBJECT_IDENTIFIER data) {
288                 StringBuilder builder = new StringBuilder();
289                 if (data.value().size() > 0) {
290                         builder.append(data.value().get(0));
291                         for (int i = 1; i < data.value().size(); i++) {
292                                 builder.append('.').append(data.value().get(i));
293                         }
294                 }
295                 return builder.toString();
296         }
297
298         /*
299          * (non-Javadoc)
300          * 
301          * @see
302          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
303          * .builtin.RELATIVE_OID)
304          */
305         @Override
306         public String visit(RELATIVE_OID data) {
307                 return visit((OBJECT_IDENTIFIER) data);
308         }
309
310         /*
311          * (non-Javadoc)
312          * 
313          * @see
314          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
315          * .builtin.REAL)
316          */
317         @Override
318         public String visit(REAL data) {
319                 if (data.value().isInfinite()) {
320                         return TAG_START
321                                         + (data.value() == Double.POSITIVE_INFINITY ? REAL_PLUS_INFINITY
322                                                         : REAL_MINUS_INFINITY) + SINGLE_TAG_END;
323                 }
324                 if (data.value() == 0) {
325                         return REAL_ZERO;
326                 }
327                 return BigDecimal.valueOf(data.value()).stripTrailingZeros()
328                                 .toPlainString();
329         }
330
331         /*
332          * (non-Javadoc)
333          * 
334          * @see
335          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
336          * .builtin.ENUMERATED)
337          */
338         @Override
339         public String visit(ENUMERATED data) {
340                 return visit((INTEGER) data);
341         }
342
343         /*
344          * (non-Javadoc)
345          * 
346          * @see
347          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
348          * .builtin.ANY)
349          */
350         @Override
351         public String visit(ANY data) throws ASN1EncodingException {
352                 encodeImpl(data.value(),
353                                 data.value().specification().xmlTypeName(module));
354                 return null;
355         }
356
357         /*
358          * (non-Javadoc)
359          * 
360          * @see
361          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
362          * .builtin.CHOICE)
363          */
364         @Override
365         public String visit(CHOICE data) throws ASN1EncodingException {
366                 StringBuilder enclosure = builder;
367                 builder = new StringBuilder();
368                 encodeImpl(data.selectedValue(), data.selectedIdentifier());
369                 String result = builder.toString();
370                 builder = enclosure;
371                 return result;
372         }
373
374         /*
375          * (non-Javadoc)
376          * 
377          * @see
378          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
379          * .builtin.SEQUENCE_OF)
380          */
381         @Override
382         public String visit(SEQUENCE_OF<? extends ASN1Type> data)
383                         throws ASN1EncodingException {
384                 return processCollection(data);
385         }
386
387         /*
388          * (non-Javadoc)
389          * 
390          * @see
391          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
392          * .builtin.SEQUENCE)
393          */
394         @Override
395         public String visit(SEQUENCE data) throws ASN1EncodingException {
396                 return processConstructive(data);
397         }
398
399         /*
400          * (non-Javadoc)
401          * 
402          * @see
403          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
404          * .builtin.SET_OF)
405          */
406         @Override
407         public String visit(SET_OF<? extends ASN1Type> data)
408                         throws ASN1EncodingException {
409                 return processCollection(data);
410         }
411
412         /*
413          * (non-Javadoc)
414          * 
415          * @see
416          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
417          * .builtin.SET)
418          */
419         @Override
420         public String visit(SET data) throws ASN1EncodingException {
421                 return processConstructive(data);
422         }
423
424         /*
425          * (non-Javadoc)
426          * 
427          * @see
428          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
429          * .StringType)
430          */
431         @Override
432         public String visit(StringType data) throws ASN1EncodingException {
433                 try {
434                         return new String(XerStringEscapeUtil.escape(data.stringValue())
435                                         .getBytes("Shift_JIS"));
436                 } catch (UnsupportedEncodingException e) {
437                         throw new ASN1EncodingException();
438                 }
439         }
440
441         /*
442          * (non-Javadoc)
443          * 
444          * @see
445          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
446          * .TimeType)
447          */
448         @Override
449         public String visit(TimeType data) throws ASN1EncodingException {
450                 return data.stringValue();
451         }
452
453         /*
454          * (non-Javadoc)
455          * 
456          * @see
457          * jp.bitmeister.asn1.processor.ASN1Visitor#visit(jp.bitmeister.asn1.type
458          * .UnknownType)
459          */
460         @Override
461         public String visit(UnknownType data) throws ASN1EncodingException {
462                 ASN1EncodingException ex = new ASN1EncodingException();
463                 ex.setMessage("Can't encode unknown type.", null, data.getClass(),
464                                 null, data);
465                 throw ex;
466         }
467
468         /**
469          * Encodes each element of the {@code ConstructiveType} data.
470          * 
471          * @param data
472          *            The data to be encoded.
473          * @throws ASN1EncodingException
474          *             When an error occurred while the encoding process.
475          */
476         private String processConstructive(ConstructiveType data)
477                         throws ASN1EncodingException {
478                 StringBuilder enclosure = builder;
479                 builder = new StringBuilder();
480                 for (ElementSpecification e : data.getElementTypeList()) {
481                         ASN1Type component = data.getComponent(e);
482                         if (component != null) {
483                                 encodeImpl(component, e.identifier());
484                         }
485                 }
486                 String result = builder.toString();
487                 builder = enclosure;
488                 return result;
489         }
490
491         /**
492          * Encodes each component of the {@code CollectionType} data.
493          * 
494          * @param data
495          *            The data to be encoded.
496          * @throws ASN1EncodingException
497          *             When an error occurred while the encoding process.
498          */
499         private String processCollection(CollectionType<? extends ASN1Type> data)
500                         throws ASN1EncodingException {
501                 StringBuilder enclosure = builder;
502                 builder = new StringBuilder();
503                 if (BOOLEAN.class.isAssignableFrom(data.componentType())
504                                 || ENUMERATED.class.isAssignableFrom(data.componentType())
505                                 || CHOICE.class.isAssignableFrom(data.componentType())) {
506                         for (ASN1Type e : data.collection()) {
507                                 builder.append(e.accept(this));
508                         }
509                 } else {
510                         for (ASN1Type e : data.collection()) {
511                                 encodeImpl(e, e.specification().xmlTypeName(module));
512                         }
513                 }
514                 String result = builder.toString();
515                 builder = enclosure;
516                 return result;
517         }
518
519 }