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.activemq.openwire.v10;
018
019import java.io.DataInput;
020import java.io.DataOutput;
021import java.io.IOException;
022import java.lang.reflect.Constructor;
023import org.apache.activemq.command.DataStructure;
024import org.apache.activemq.openwire.BooleanStream;
025import org.apache.activemq.openwire.DataStreamMarshaller;
026import org.apache.activemq.openwire.OpenWireFormat;
027import org.apache.activemq.openwire.OpenWireUtil;
028import org.apache.activemq.util.ByteSequence;
029
030public abstract class BaseDataStreamMarshaller implements DataStreamMarshaller {
031
032    public static final Constructor STACK_TRACE_ELEMENT_CONSTRUCTOR;
033    private static final int MAX_EXCEPTION_MESSAGE_SIZE = 1024;
034
035    static {
036        Constructor constructor = null;
037        try {
038            constructor = StackTraceElement.class.getConstructor(new Class[] {String.class, String.class,
039                                                                              String.class, int.class});
040        } catch (Throwable e) {
041        }
042        STACK_TRACE_ELEMENT_CONSTRUCTOR = constructor;
043    }
044
045    public abstract byte getDataStructureType();
046
047    public abstract DataStructure createObject();
048
049    public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
050        return 0;
051    }
052
053    public void tightMarshal2(OpenWireFormat wireFormat, Object o, DataOutput dataOut, BooleanStream bs)
054        throws IOException {
055    }
056
057    public void tightUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn, BooleanStream bs)
058        throws IOException {
059    }
060
061    public int tightMarshalLong1(OpenWireFormat wireFormat, long o, BooleanStream bs) throws IOException {
062        if (o == 0) {
063            bs.writeBoolean(false);
064            bs.writeBoolean(false);
065            return 0;
066        } else if ((o & 0xFFFFFFFFFFFF0000L) == 0) {
067            bs.writeBoolean(false);
068            bs.writeBoolean(true);
069            return 2;
070        } else if ((o & 0xFFFFFFFF00000000L) == 0) {
071            bs.writeBoolean(true);
072            bs.writeBoolean(false);
073            return 4;
074        } else {
075            bs.writeBoolean(true);
076            bs.writeBoolean(true);
077            return 8;
078        }
079    }
080
081    public void tightMarshalLong2(OpenWireFormat wireFormat, long o, DataOutput dataOut, BooleanStream bs)
082        throws IOException {
083        if (bs.readBoolean()) {
084            if (bs.readBoolean()) {
085                dataOut.writeLong(o);
086            } else {
087                dataOut.writeInt((int)o);
088            }
089        } else {
090            if (bs.readBoolean()) {
091                dataOut.writeShort((int)o);
092            }
093        }
094    }
095
096    public long tightUnmarshalLong(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs)
097        throws IOException {
098        if (bs.readBoolean()) {
099            if (bs.readBoolean()) {
100                return dataIn.readLong();
101            } else {
102                return toLong(dataIn.readInt());
103            }
104        } else {
105            if (bs.readBoolean()) {
106                return toLong(dataIn.readShort());
107            } else {
108                return 0;
109            }
110        }
111    }
112
113    protected long toLong(short value) {
114        // lets handle negative values
115        long answer = value;
116        return answer & 0xffffL;
117    }
118
119    protected long toLong(int value) {
120        // lets handle negative values
121        long answer = value;
122        return answer & 0xffffffffL;
123    }
124
125    protected DataStructure tightUnmarsalNestedObject(OpenWireFormat wireFormat, DataInput dataIn,
126                                                      BooleanStream bs) throws IOException {
127        return wireFormat.tightUnmarshalNestedObject(dataIn, bs);
128    }
129
130    protected int tightMarshalNestedObject1(OpenWireFormat wireFormat, DataStructure o, BooleanStream bs)
131        throws IOException {
132        return wireFormat.tightMarshalNestedObject1(o, bs);
133    }
134
135    protected void tightMarshalNestedObject2(OpenWireFormat wireFormat, DataStructure o, DataOutput dataOut,
136                                             BooleanStream bs) throws IOException {
137        wireFormat.tightMarshalNestedObject2(o, dataOut, bs);
138    }
139
140    protected DataStructure tightUnmarsalCachedObject(OpenWireFormat wireFormat, DataInput dataIn,
141                                                      BooleanStream bs) throws IOException {
142        if (wireFormat.isCacheEnabled()) {
143            if (bs.readBoolean()) {
144                short index = dataIn.readShort();
145                DataStructure object = wireFormat.tightUnmarshalNestedObject(dataIn, bs);
146                wireFormat.setInUnmarshallCache(index, object);
147                return object;
148            } else {
149                short index = dataIn.readShort();
150                return wireFormat.getFromUnmarshallCache(index);
151            }
152        } else {
153            return wireFormat.tightUnmarshalNestedObject(dataIn, bs);
154        }
155    }
156
157    protected int tightMarshalCachedObject1(OpenWireFormat wireFormat, DataStructure o, BooleanStream bs)
158        throws IOException {
159        if (wireFormat.isCacheEnabled()) {
160            Short index = wireFormat.getMarshallCacheIndex(o);
161            bs.writeBoolean(index == null);
162            if (index == null) {
163                int rc = wireFormat.tightMarshalNestedObject1(o, bs);
164                wireFormat.addToMarshallCache(o);
165                return 2 + rc;
166            } else {
167                return 2;
168            }
169        } else {
170            return wireFormat.tightMarshalNestedObject1(o, bs);
171        }
172    }
173
174    protected void tightMarshalCachedObject2(OpenWireFormat wireFormat, DataStructure o, DataOutput dataOut,
175                                             BooleanStream bs) throws IOException {
176        if (wireFormat.isCacheEnabled()) {
177            Short index = wireFormat.getMarshallCacheIndex(o);
178            if (bs.readBoolean()) {
179                dataOut.writeShort(index.shortValue());
180                wireFormat.tightMarshalNestedObject2(o, dataOut, bs);
181            } else {
182                dataOut.writeShort(index.shortValue());
183            }
184        } else {
185            wireFormat.tightMarshalNestedObject2(o, dataOut, bs);
186        }
187    }
188
189    protected Throwable tightUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs)
190        throws IOException {
191        if (bs.readBoolean()) {
192            String clazz = tightUnmarshalString(dataIn, bs);
193            String message = tightUnmarshalString(dataIn, bs);
194            Throwable o = createThrowable(clazz, message);
195            if (wireFormat.isStackTraceEnabled()) {
196                if (STACK_TRACE_ELEMENT_CONSTRUCTOR != null) {
197                    StackTraceElement ss[] = new StackTraceElement[dataIn.readShort()];
198                    for (int i = 0; i < ss.length; i++) {
199                        try {
200                            ss[i] = (StackTraceElement)STACK_TRACE_ELEMENT_CONSTRUCTOR
201                                .newInstance(new Object[] {tightUnmarshalString(dataIn, bs),
202                                                           tightUnmarshalString(dataIn, bs),
203                                                           tightUnmarshalString(dataIn, bs),
204                                                           Integer.valueOf(dataIn.readInt())});
205                        } catch (IOException e) {
206                            throw e;
207                        } catch (Throwable e) {
208                        }
209                    }
210                    o.setStackTrace(ss);
211                } else {
212                    short size = dataIn.readShort();
213                    for (int i = 0; i < size; i++) {
214                        tightUnmarshalString(dataIn, bs);
215                        tightUnmarshalString(dataIn, bs);
216                        tightUnmarshalString(dataIn, bs);
217                        dataIn.readInt();
218                    }
219                }
220                o.initCause(tightUnmarsalThrowable(wireFormat, dataIn, bs));
221
222            }
223            return o;
224        } else {
225            return null;
226        }
227    }
228
229    private Throwable createThrowable(String className, String message) {
230        try {
231            Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader());
232            OpenWireUtil.validateIsThrowable(clazz);
233            Constructor constructor = clazz.getConstructor(new Class[] {String.class});
234            return (Throwable)constructor.newInstance(new Object[] {message});
235        } catch (IllegalArgumentException e) {
236            return e;
237        } catch (Throwable e) {
238            return new Throwable(className + ": " + message);
239        }
240    }
241
242    protected int tightMarshalThrowable1(OpenWireFormat wireFormat, Throwable o, BooleanStream bs)
243        throws IOException {
244        if (o == null) {
245            bs.writeBoolean(false);
246            return 0;
247        } else {
248            int rc = 0;
249            bs.writeBoolean(true);
250            rc += tightMarshalString1(o.getClass().getName(), bs);
251            rc += tightMarshalString1(cutMessageIfNeeded(o.getMessage()), bs);
252            if (wireFormat.isStackTraceEnabled()) {
253                rc += 2;
254                StackTraceElement[] stackTrace = o.getStackTrace();
255                for (int i = 0; i < stackTrace.length; i++) {
256                    StackTraceElement element = stackTrace[i];
257                    rc += tightMarshalString1(element.getClassName(), bs);
258                    rc += tightMarshalString1(element.getMethodName(), bs);
259                    rc += tightMarshalString1(element.getFileName(), bs);
260                    rc += 4;
261                }
262                rc += tightMarshalThrowable1(wireFormat, o.getCause(), bs);
263            }
264            return rc;
265        }
266    }
267
268    protected void tightMarshalThrowable2(OpenWireFormat wireFormat, Throwable o, DataOutput dataOut,
269                                          BooleanStream bs) throws IOException {
270        if (bs.readBoolean()) {
271            tightMarshalString2(o.getClass().getName(), dataOut, bs);
272            tightMarshalString2(cutMessageIfNeeded(o.getMessage()), dataOut, bs);
273            if (wireFormat.isStackTraceEnabled()) {
274                StackTraceElement[] stackTrace = o.getStackTrace();
275                dataOut.writeShort(stackTrace.length);
276                for (int i = 0; i < stackTrace.length; i++) {
277                    StackTraceElement element = stackTrace[i];
278                    tightMarshalString2(element.getClassName(), dataOut, bs);
279                    tightMarshalString2(element.getMethodName(), dataOut, bs);
280                    tightMarshalString2(element.getFileName(), dataOut, bs);
281                    dataOut.writeInt(element.getLineNumber());
282                }
283                tightMarshalThrowable2(wireFormat, o.getCause(), dataOut, bs);
284            }
285        }
286    }
287
288    @SuppressWarnings("deprecation")
289    protected String tightUnmarshalString(DataInput dataIn, BooleanStream bs) throws IOException {
290        if (bs.readBoolean()) {
291            if (bs.readBoolean()) {
292                int size = dataIn.readShort();
293                byte data[] = new byte[size];
294                dataIn.readFully(data);
295                // Yes deprecated, but we know what we are doing.
296                // This allows us to create a String from a ASCII byte array. (no UTF-8 decoding)
297                return new String(data, 0);
298            } else {
299                return dataIn.readUTF();
300            }
301        } else {
302            return null;
303        }
304    }
305
306    protected int tightMarshalString1(String value, BooleanStream bs) throws IOException {
307        bs.writeBoolean(value != null);
308        if (value != null) {
309
310            int strlen = value.length();
311            int utflen = 0;
312            char[] charr = new char[strlen];
313            int c = 0;
314            boolean isOnlyAscii = true;
315
316            value.getChars(0, strlen, charr, 0);
317
318            for (int i = 0; i < strlen; i++) {
319                c = charr[i];
320                if ((c >= 0x0001) && (c <= 0x007F)) {
321                    utflen++;
322                } else if (c > 0x07FF) {
323                    utflen += 3;
324                    isOnlyAscii = false;
325                } else {
326                    isOnlyAscii = false;
327                    utflen += 2;
328                }
329            }
330
331            if (utflen >= Short.MAX_VALUE) {
332                throw new IOException("Encountered a String value that is too long to encode.");
333            }
334            bs.writeBoolean(isOnlyAscii);
335            return utflen + 2;
336
337        } else {
338            return 0;
339        }
340    }
341
342    protected void tightMarshalString2(String value, DataOutput dataOut, BooleanStream bs) throws IOException {
343        if (bs.readBoolean()) {
344            // If we verified it only holds ascii values
345            if (bs.readBoolean()) {
346                dataOut.writeShort(value.length());
347                dataOut.writeBytes(value);
348            } else {
349                dataOut.writeUTF(value);
350            }
351        }
352    }
353
354    protected int tightMarshalObjectArray1(OpenWireFormat wireFormat, DataStructure[] objects,
355                                           BooleanStream bs) throws IOException {
356        if (objects != null) {
357            int rc = 0;
358            bs.writeBoolean(true);
359            rc += 2;
360            for (int i = 0; i < objects.length; i++) {
361                rc += tightMarshalNestedObject1(wireFormat, objects[i], bs);
362            }
363            return rc;
364        } else {
365            bs.writeBoolean(false);
366            return 0;
367        }
368    }
369
370    protected void tightMarshalObjectArray2(OpenWireFormat wireFormat, DataStructure[] objects,
371                                            DataOutput dataOut, BooleanStream bs) throws IOException {
372        if (bs.readBoolean()) {
373            dataOut.writeShort(objects.length);
374            for (int i = 0; i < objects.length; i++) {
375                tightMarshalNestedObject2(wireFormat, objects[i], dataOut, bs);
376            }
377        }
378    }
379
380    protected int tightMarshalConstByteArray1(byte[] data, BooleanStream bs, int i) throws IOException {
381        return i;
382    }
383
384    protected void tightMarshalConstByteArray2(byte[] data, DataOutput dataOut, BooleanStream bs, int i)
385        throws IOException {
386        dataOut.write(data, 0, i);
387    }
388
389    protected byte[] tightUnmarshalConstByteArray(DataInput dataIn, BooleanStream bs, int i)
390        throws IOException {
391        byte data[] = new byte[i];
392        dataIn.readFully(data);
393        return data;
394    }
395
396    protected int tightMarshalByteArray1(byte[] data, BooleanStream bs) throws IOException {
397        bs.writeBoolean(data != null);
398        if (data != null) {
399            return data.length + 4;
400        } else {
401            return 0;
402        }
403    }
404
405    protected void tightMarshalByteArray2(byte[] data, DataOutput dataOut, BooleanStream bs)
406        throws IOException {
407        if (bs.readBoolean()) {
408            dataOut.writeInt(data.length);
409            dataOut.write(data);
410        }
411    }
412
413    protected byte[] tightUnmarshalByteArray(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs) throws IOException {
414        byte rc[] = null;
415        if (bs.readBoolean()) {
416            int size = dataIn.readInt();
417            OpenWireUtil.validateBufferSize(wireFormat, size);
418            rc = new byte[size];
419            dataIn.readFully(rc);
420        }
421        return rc;
422    }
423
424    protected int tightMarshalByteSequence1(ByteSequence data, BooleanStream bs) throws IOException {
425        bs.writeBoolean(data != null);
426        if (data != null) {
427            return data.getLength() + 4;
428        } else {
429            return 0;
430        }
431    }
432
433    protected void tightMarshalByteSequence2(ByteSequence data, DataOutput dataOut, BooleanStream bs)
434        throws IOException {
435        if (bs.readBoolean()) {
436            dataOut.writeInt(data.getLength());
437            dataOut.write(data.getData(), data.getOffset(), data.getLength());
438        }
439    }
440
441    protected ByteSequence tightUnmarshalByteSequence(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs) throws IOException {
442        ByteSequence rc = null;
443        if (bs.readBoolean()) {
444            int size = dataIn.readInt();
445            OpenWireUtil.validateBufferSize(wireFormat, size);
446            byte[] t = new byte[size];
447            dataIn.readFully(t);
448            return new ByteSequence(t, 0, size);
449        }
450        return rc;
451    }
452
453    //
454    // The loose marshaling logic
455    //
456
457    public void looseMarshal(OpenWireFormat wireFormat, Object o, DataOutput dataOut) throws IOException {
458    }
459
460    public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
461    }
462
463    public void looseMarshalLong(OpenWireFormat wireFormat, long o, DataOutput dataOut) throws IOException {
464        dataOut.writeLong(o);
465    }
466
467    public long looseUnmarshalLong(OpenWireFormat wireFormat, DataInput dataIn) throws IOException {
468        return dataIn.readLong();
469    }
470
471    protected DataStructure looseUnmarsalNestedObject(OpenWireFormat wireFormat, DataInput dataIn)
472        throws IOException {
473        return wireFormat.looseUnmarshalNestedObject(dataIn);
474    }
475
476    protected void looseMarshalNestedObject(OpenWireFormat wireFormat, DataStructure o, DataOutput dataOut)
477        throws IOException {
478        wireFormat.looseMarshalNestedObject(o, dataOut);
479    }
480
481    protected DataStructure looseUnmarsalCachedObject(OpenWireFormat wireFormat, DataInput dataIn)
482        throws IOException {
483        if (wireFormat.isCacheEnabled()) {
484            if (dataIn.readBoolean()) {
485                short index = dataIn.readShort();
486                DataStructure object = wireFormat.looseUnmarshalNestedObject(dataIn);
487                wireFormat.setInUnmarshallCache(index, object);
488                return object;
489            } else {
490                short index = dataIn.readShort();
491                return wireFormat.getFromUnmarshallCache(index);
492            }
493        } else {
494            return wireFormat.looseUnmarshalNestedObject(dataIn);
495        }
496    }
497
498    protected void looseMarshalCachedObject(OpenWireFormat wireFormat, DataStructure o, DataOutput dataOut)
499        throws IOException {
500        if (wireFormat.isCacheEnabled()) {
501            Short index = wireFormat.getMarshallCacheIndex(o);
502            dataOut.writeBoolean(index == null);
503            if (index == null) {
504                index = wireFormat.addToMarshallCache(o);
505                dataOut.writeShort(index.shortValue());
506                wireFormat.looseMarshalNestedObject(o, dataOut);
507            } else {
508                dataOut.writeShort(index.shortValue());
509            }
510        } else {
511            wireFormat.looseMarshalNestedObject(o, dataOut);
512        }
513    }
514
515    protected Throwable looseUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn)
516        throws IOException {
517        if (dataIn.readBoolean()) {
518            String clazz = looseUnmarshalString(dataIn);
519            String message = looseUnmarshalString(dataIn);
520            Throwable o = createThrowable(clazz, message);
521            if (wireFormat.isStackTraceEnabled()) {
522                if (STACK_TRACE_ELEMENT_CONSTRUCTOR != null) {
523                    StackTraceElement ss[] = new StackTraceElement[dataIn.readShort()];
524                    for (int i = 0; i < ss.length; i++) {
525                        try {
526                            ss[i] = (StackTraceElement)STACK_TRACE_ELEMENT_CONSTRUCTOR
527                                .newInstance(new Object[] {looseUnmarshalString(dataIn),
528                                                           looseUnmarshalString(dataIn),
529                                                           looseUnmarshalString(dataIn),
530                                                           Integer.valueOf(dataIn.readInt())});
531                        } catch (IOException e) {
532                            throw e;
533                        } catch (Throwable e) {
534                        }
535                    }
536                    o.setStackTrace(ss);
537                } else {
538                    short size = dataIn.readShort();
539                    for (int i = 0; i < size; i++) {
540                        looseUnmarshalString(dataIn);
541                        looseUnmarshalString(dataIn);
542                        looseUnmarshalString(dataIn);
543                        dataIn.readInt();
544                    }
545                }
546                o.initCause(looseUnmarsalThrowable(wireFormat, dataIn));
547
548            }
549            return o;
550        } else {
551            return null;
552        }
553    }
554
555    protected void looseMarshalThrowable(OpenWireFormat wireFormat, Throwable o, DataOutput dataOut)
556        throws IOException {
557        dataOut.writeBoolean(o != null);
558        if (o != null) {
559            looseMarshalString(o.getClass().getName(), dataOut);
560            looseMarshalString(cutMessageIfNeeded(o.getMessage()), dataOut);
561            if (wireFormat.isStackTraceEnabled()) {
562                StackTraceElement[] stackTrace = o.getStackTrace();
563                dataOut.writeShort(stackTrace.length);
564                for (int i = 0; i < stackTrace.length; i++) {
565                    StackTraceElement element = stackTrace[i];
566                    looseMarshalString(element.getClassName(), dataOut);
567                    looseMarshalString(element.getMethodName(), dataOut);
568                    looseMarshalString(element.getFileName(), dataOut);
569                    dataOut.writeInt(element.getLineNumber());
570                }
571                looseMarshalThrowable(wireFormat, o.getCause(), dataOut);
572            }
573        }
574    }
575
576    protected String looseUnmarshalString(DataInput dataIn) throws IOException {
577        if (dataIn.readBoolean()) {
578            return dataIn.readUTF();
579        } else {
580            return null;
581        }
582    }
583
584    protected void looseMarshalString(String value, DataOutput dataOut) throws IOException {
585        dataOut.writeBoolean(value != null);
586        if (value != null) {
587            dataOut.writeUTF(value);
588        }
589    }
590
591    protected void looseMarshalObjectArray(OpenWireFormat wireFormat, DataStructure[] objects,
592                                           DataOutput dataOut) throws IOException {
593        dataOut.writeBoolean(objects != null);
594        if (objects != null) {
595            dataOut.writeShort(objects.length);
596            for (int i = 0; i < objects.length; i++) {
597                looseMarshalNestedObject(wireFormat, objects[i], dataOut);
598            }
599        }
600    }
601
602    protected void looseMarshalConstByteArray(OpenWireFormat wireFormat, byte[] data, DataOutput dataOut,
603                                              int i) throws IOException {
604        dataOut.write(data, 0, i);
605    }
606
607    protected byte[] looseUnmarshalConstByteArray(DataInput dataIn, int i) throws IOException {
608        byte data[] = new byte[i];
609        dataIn.readFully(data);
610        return data;
611    }
612
613    protected void looseMarshalByteArray(OpenWireFormat wireFormat, byte[] data, DataOutput dataOut)
614        throws IOException {
615        dataOut.writeBoolean(data != null);
616        if (data != null) {
617            dataOut.writeInt(data.length);
618            dataOut.write(data);
619        }
620    }
621
622    protected byte[] looseUnmarshalByteArray(OpenWireFormat wireFormat, DataInput dataIn) throws IOException {
623        byte rc[] = null;
624        if (dataIn.readBoolean()) {
625            int size = dataIn.readInt();
626            OpenWireUtil.validateBufferSize(wireFormat, size);
627            rc = new byte[size];
628            dataIn.readFully(rc);
629        }
630        return rc;
631    }
632
633    protected void looseMarshalByteSequence(OpenWireFormat wireFormat, ByteSequence data, DataOutput dataOut)
634        throws IOException {
635        dataOut.writeBoolean(data != null);
636        if (data != null) {
637            dataOut.writeInt(data.getLength());
638            dataOut.write(data.getData(), data.getOffset(), data.getLength());
639        }
640    }
641
642    protected ByteSequence looseUnmarshalByteSequence(OpenWireFormat wireFormat, DataInput dataIn) throws IOException {
643        ByteSequence rc = null;
644        if (dataIn.readBoolean()) {
645            int size = dataIn.readInt();
646            OpenWireUtil.validateBufferSize(wireFormat, size);
647            byte[] t = new byte[size];
648            dataIn.readFully(t);
649            rc = new ByteSequence(t, 0, size);
650        }
651        return rc;
652    }
653    
654    protected String cutMessageIfNeeded(final String message) {
655        return (message.length() > MAX_EXCEPTION_MESSAGE_SIZE)?
656            message.substring(0, MAX_EXCEPTION_MESSAGE_SIZE - 3) + "..." : message;
657            
658    }
659}