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