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.commons.imaging.internal;
018
019import java.awt.color.ICC_Profile;
020import java.io.File;
021import java.text.DateFormat;
022import java.text.SimpleDateFormat;
023import java.util.ArrayList;
024import java.util.Calendar;
025import java.util.Date;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Objects;
030import java.util.logging.Level;
031import java.util.logging.Logger;
032
033/**
034 * Internal-only debug class. Used for collecting extra information when parsing or modifying images or metadata. These methods are useful for troubleshooting
035 * and issue analysis, but this should not be used directly by end-users, nor extended in any way. This may change or be removed at any time.
036 */
037public final class Debug {
038
039    private static final Logger LOGGER = Logger.getLogger(Debug.class.getName());
040
041    // public static String newline = System.getProperty("line.separator");
042    private static final String NEWLINE = "\r\n";
043    private static long counter;
044
045    private static String byteQuadToString(final int byteQuad) {
046        final byte b1 = (byte) (byteQuad >> 24 & 0xff);
047        final byte b2 = (byte) (byteQuad >> 16 & 0xff);
048        final byte b3 = (byte) (byteQuad >> 8 & 0xff);
049        final byte b4 = (byte) (byteQuad >> 0 & 0xff);
050
051        final char c1 = (char) b1;
052        final char c2 = (char) b2;
053        final char c3 = (char) b3;
054        final char c4 = (char) b4;
055        // return new String(new char[] { c1, c2, c3, c4 });
056        final StringBuilder buffer = new StringBuilder(31);
057        buffer.append(new String(new char[] { c1, c2, c3, c4 }));
058        buffer.append(" byteQuad: ");
059        buffer.append(byteQuad);
060        buffer.append(" b1: ");
061        buffer.append(b1);
062        buffer.append(" b2: ");
063        buffer.append(b2);
064        buffer.append(" b3: ");
065        buffer.append(b3);
066        buffer.append(" b4: ");
067        buffer.append(b4);
068
069        return buffer.toString();
070    }
071
072    public static void debug() {
073        if (LOGGER.isLoggable(Level.FINEST)) {
074            LOGGER.finest(NEWLINE);
075        }
076    }
077
078    public static void debug(final String message) {
079        if (LOGGER.isLoggable(Level.FINEST)) {
080            LOGGER.finest(message);
081        }
082    }
083
084    private static void debug(final String message, final byte[] v) {
085        debug(getDebug(message, v));
086    }
087
088    private static void debug(final String message, final Calendar value) {
089        final DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.ENGLISH);
090        debug(message, value == null ? "null" : df.format(value.getTime()));
091    }
092
093    private static void debug(final String message, final char[] v) {
094        debug(getDebug(message, v));
095    }
096
097    private static void debug(final String message, final Date value) {
098        final DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.ENGLISH);
099        debug(message, value == null ? "null" : df.format(value));
100    }
101
102    private static void debug(final String message, final File file) {
103        debug(message + ": " + (file == null ? "null" : file.getPath()));
104    }
105
106    private static void debug(final String message, final ICC_Profile value) {
107        debug("ICC_Profile " + message + ": " + Objects.toString(value));
108        if (value != null) {
109            debug("\t getProfileClass: " + byteQuadToString(value.getProfileClass()));
110            debug("\t getPCSType: " + byteQuadToString(value.getPCSType()));
111            debug("\t getColorSpaceType() : " + byteQuadToString(value.getColorSpaceType()));
112        }
113    }
114
115    private static void debug(final String message, final int[] v) {
116        debug(getDebug(message, v));
117    }
118
119    private static void debug(final String message, final List<?> v) {
120        final String suffix = " [" + counter++ + "]";
121
122        debug(message + " (" + v.size() + ")" + suffix);
123        for (final Object aV : v) {
124            debug("\t" + aV.toString() + suffix);
125        }
126        debug();
127    }
128
129    private static void debug(final String message, final Map<?, ?> map) {
130        debug(getDebug(message, map));
131    }
132
133    public static void debug(final String message, final Object value) {
134        if (value == null) {
135            debug(message, "null");
136        } else if (value instanceof char[]) {
137            debug(message, (char[]) value);
138        } else if (value instanceof byte[]) {
139            debug(message, (byte[]) value);
140        } else if (value instanceof int[]) {
141            debug(message, (int[]) value);
142        } else if (value instanceof String) {
143            debug(message, (String) value);
144        } else if (value instanceof List) {
145            debug(message, (List<?>) value);
146        } else if (value instanceof Map) {
147            debug(message, (Map<?, ?>) value);
148        } else if (value instanceof ICC_Profile) {
149            debug(message, (ICC_Profile) value);
150        } else if (value instanceof File) {
151            debug(message, (File) value);
152        } else if (value instanceof Date) {
153            debug(message, (Date) value);
154        } else if (value instanceof Calendar) {
155            debug(message, (Calendar) value);
156        } else {
157            debug(message, value.toString());
158        }
159    }
160
161    private static void debug(final String message, final String value) {
162        debug(message + " " + value);
163    }
164
165    public static void debug(final Throwable e) {
166        debug(getDebug(e));
167    }
168
169    public static void debug(final Throwable e, final int value) {
170        debug(getDebug(e, value));
171    }
172
173    private static String getDebug(final String message, final byte[] v) {
174        final int max = 250;
175        return getDebug(message, v, max);
176    }
177
178    private static String getDebug(final String message, final byte[] v, final int max) {
179
180        final StringBuilder result = new StringBuilder();
181
182        if (v == null) {
183            result.append(message + " (" + null + ")" + NEWLINE);
184        } else {
185            result.append(message + " (" + v.length + ")" + NEWLINE);
186            for (int i = 0; i < max && i < v.length; i++) {
187                final int b = 0xff & v[i];
188
189                char c;
190                if (b == 0 || b == 10 || b == 11 || b == 13) {
191                    c = ' ';
192                } else {
193                    c = (char) b;
194                }
195
196                result.append("\t" + i + ": " + b + " (" + c + ", 0x" + Integer.toHexString(b) + ")" + NEWLINE);
197            }
198            if (v.length > max) {
199                result.append("\t..." + NEWLINE);
200            }
201
202            result.append(NEWLINE);
203        }
204        return result.toString();
205    }
206
207    private static String getDebug(final String message, final char[] v) {
208        final StringBuilder result = new StringBuilder();
209
210        if (v == null) {
211            result.append(message + " (" + null + ")" + NEWLINE);
212        } else {
213            result.append(message + " (" + v.length + ")" + NEWLINE);
214            for (final char element : v) {
215                result.append("\t" + element + " (" + (0xff & element) + ")" + NEWLINE);
216            }
217            result.append(NEWLINE);
218        }
219        return result.toString();
220    }
221
222    private static String getDebug(final String message, final int[] v) {
223        final StringBuilder result = new StringBuilder();
224
225        if (v == null) {
226            result.append(message + " (" + null + ")" + NEWLINE);
227        } else {
228            result.append(message + " (" + v.length + ")" + NEWLINE);
229            for (final int element : v) {
230                result.append("\t" + element + NEWLINE);
231            }
232            result.append(NEWLINE);
233        }
234        return result.toString();
235    }
236
237    private static String getDebug(final String message, final Map<?, ?> map) {
238        final StringBuilder result = new StringBuilder();
239
240        if (map == null) {
241            return message + " map: " + null;
242        }
243
244        final List<Object> keys = new ArrayList<>(map.keySet());
245        result.append(message + " map: " + keys.size() + NEWLINE);
246        for (int i = 0; i < keys.size(); i++) {
247            final Object key = keys.get(i);
248            final Object value = map.get(key);
249            result.append("\t" + i + ": '" + key + "' -> '" + value + "'" + NEWLINE);
250        }
251
252        result.append(NEWLINE);
253
254        return result.toString();
255    }
256
257    private static String getDebug(final Throwable e) {
258        return getDebug(e, -1);
259    }
260
261    private static String getDebug(final Throwable e, final int max) {
262        final StringBuilder result = new StringBuilder(35);
263
264        final SimpleDateFormat timestamp = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss:SSS", Locale.ENGLISH);
265        final String datetime = timestamp.format(new Date()).toLowerCase();
266
267        result.append(NEWLINE);
268        result.append("Throwable: " + (e == null ? "" : "(" + e.getClass().getName() + ")") + ":" + datetime + NEWLINE);
269        result.append("Throwable: " + (e == null ? "null" : e.getLocalizedMessage()) + NEWLINE);
270        result.append(NEWLINE);
271
272        result.append(getStackTrace(e, max));
273
274        result.append("Caught here:" + NEWLINE);
275        result.append(getStackTrace(new Exception(), max, 1));
276        // Debug.dumpStack();
277        result.append(NEWLINE);
278        return result.toString();
279    }
280
281    private static String getStackTrace(final Throwable e, final int limit) {
282        return getStackTrace(e, limit, 0);
283    }
284
285    private static String getStackTrace(final Throwable e, final int limit, final int skip) {
286        final StringBuilder result = new StringBuilder();
287
288        if (e != null) {
289            final StackTraceElement[] stes = e.getStackTrace();
290            if (stes != null) {
291                for (int i = skip; i < stes.length && (limit < 0 || i < limit); i++) {
292                    final StackTraceElement ste = stes[i];
293
294                    result.append(
295                            "\tat " + ste.getClassName() + "." + ste.getMethodName() + "(" + ste.getFileName() + ":" + ste.getLineNumber() + ")" + NEWLINE);
296                }
297                if (limit >= 0 && stes.length > limit) {
298                    result.append("\t..." + NEWLINE);
299                }
300            }
301
302            // e.printStackTrace(System.out);
303            result.append(NEWLINE);
304        }
305
306        return result.toString();
307    }
308
309    private Debug() {
310    }
311}