/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.sshd.common.util.logging; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.function.Predicate; import java.util.logging.Level; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.ReflectionUtils; import org.slf4j.Logger; /** * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> */ public final class LoggingUtils { private LoggingUtils() { throw new UnsupportedOperationException("No instance"); } /** * Scans using reflection API for all fields that are {@code public static final} * that start with the given common prefix (case <U>sensitive</U>) and are of type * {@link Number}. * * @param clazz The {@link Class} to query * @param commonPrefix The expected common prefix * @return A {@link Map} of all the matching fields, where key=the field's {@link Integer} * value and mapping=the field's name * @see #generateMnemonicMap(Class, Predicate) */ public static Map<Integer, String> generateMnemonicMap(Class<?> clazz, final String commonPrefix) { return generateMnemonicMap(clazz, f -> { String name = f.getName(); return name.startsWith(commonPrefix); }); } /** * Scans using reflection API for all <U>numeric {@code public static final}</U> fields * that are also accepted by the predicate. Any field that is not such or fail to retrieve * its value, or has a duplicate value is <U>silently</U> skipped. * * @param clazz The {@link Class} to query * @param acceptor The {@link Predicate} used to decide whether to process the {@link Field} * (besides being a {@link Number} and {@code public static final}). * @return A {@link Map} of all the matching fields, where key=the field's {@link Integer} * value and mapping=the field's name * @see #getMnemonicFields(Class, Predicate) */ public static Map<Integer, String> generateMnemonicMap(Class<?> clazz, Predicate<? super Field> acceptor) { Collection<Field> fields = getMnemonicFields(clazz, acceptor); if (GenericUtils.isEmpty(fields)) { return Collections.emptyMap(); } Map<Integer, String> result = new HashMap<>(fields.size()); for (Field f : fields) { String name = f.getName(); try { Number value = (Number) f.get(null); String prev = result.put(NumberUtils.toInteger(value), name); if (prev != null) { //noinspection UnnecessaryContinue continue; // debug breakpoint } } catch (Exception e) { //noinspection UnnecessaryContinue continue; // debug breakpoint } } return result; } /** * Scans using reflection API for all <U>numeric {@code public static final}</U> fields * that have a common prefix and whose value is used by several of the other * matching fields * * @param clazz The {@link Class} to query * @param commonPrefix The expected common prefix * @return A {@link Map} of all the mnemonic fields names whose value is the same as other * fields in this map. The key is the field's name and value is its associated opcode. * @see #getAmbiguousMenmonics(Class, Predicate) */ public static Map<String, Integer> getAmbiguousMenmonics(Class<?> clazz, String commonPrefix) { return getAmbiguousMenmonics(clazz, f -> { String name = f.getName(); return name.startsWith(commonPrefix); }); } /** * Scans using reflection API for all <U>numeric {@code public static final}</U> fields * that are also accepted by the predicate and whose value is used by several of the other * matching fields * * @param clazz The {@link Class} to query * @param acceptor The {@link Predicate} used to decide whether to process the {@link Field} * (besides being a {@link Number} and {@code public static final}). * @return A {@link Map} of all the mnemonic fields names whose value is the same as other * fields in this map. The key is the field's name and value is its associated opcode. * @see #getMnemonicFields(Class, Predicate) */ public static Map<String, Integer> getAmbiguousMenmonics(Class<?> clazz, Predicate<? super Field> acceptor) { Collection<Field> fields = getMnemonicFields(clazz, acceptor); if (GenericUtils.isEmpty(fields)) { return Collections.emptyMap(); } Map<String, Integer> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Map<Integer, List<String>> opcodesMap = new HashMap<>(fields.size()); for (Field f : fields) { String name = f.getName(); try { Number value = (Number) f.get(null); Integer key = NumberUtils.toInteger(value); List<String> nameList = opcodesMap.get(key); if (nameList == null) { nameList = new ArrayList<>(); opcodesMap.put(key, nameList); } nameList.add(name); int numOpcodes = nameList.size(); if (numOpcodes > 1) { result.put(name, key); if (numOpcodes == 2) { // add the 1st name as well result.put(nameList.get(0), key); } } } catch (Exception e) { continue; // debug breakpoint } } return result; } /** * Scans using reflection API for all <U>numeric {@code public static final}</U> fields * that are also accepted by the predicate. * * @param clazz The {@link Class} to query * @param acceptor The {@link Predicate} used to decide whether to process the {@link Field} * (besides being a {@link Number} and {@code public static final}). * @return A {@link Collection} of all the fields that have satisfied all conditions */ public static Collection<Field> getMnemonicFields(Class<?> clazz, Predicate<? super Field> acceptor) { return ReflectionUtils.getMatchingFields(clazz, f -> { int mods = f.getModifiers(); if ((!Modifier.isPublic(mods)) || (!Modifier.isStatic(mods)) || (!Modifier.isFinal(mods))) { return false; } Class<?> type = f.getType(); if (!NumberUtils.isNumericClass(type)) { return false; } return acceptor.test(f); }); } /** * Verifies if the given level is above the required threshold for logging. * * @param level The {@link Level} to evaluate * @param threshold The threshold {@link Level} * @return {@code true} if the evaluated level is above the required * threshold. * <P> * <B>Note(s):</B> * </P> * <UL> * <LI><P> * If either argument is {@code null} then result is {@code false}. * </P></LI> * * <LI><P> * If the evaluated level is {@link Level#OFF} then result is {@code false} * regardless of the threshold. * </P></LI> * * <LI><P> * If the threshold is {@link Level#ALL} and the evaluated level is * <U>not</U> {@link Level#OFF} the result is {@code true}. * </P></LI> * * <LI><P> * Otherwise, the evaluated level {@link Level#intValue()} must be * greater or equal to the threshold. * </P></LI> * </UL> */ public static boolean isLoggable(Level level, Level threshold) { if ((level == null) || (threshold == null)) { return false; } else if (Level.OFF.equals(level) || Level.OFF.equals(threshold)) { return false; } else if (Level.ALL.equals(threshold)) { return true; } else { return level.intValue() >= threshold.intValue(); } } public static SimplifiedLog wrap(final Logger logger) { if (logger == null) { return SimplifiedLog.EMPTY; } else { return new SimplifiedLog() { @Override public void log(Level level, Object message, Throwable t) { if (isEnabled(level)) { logMessage(logger, level, message, t); } } @Override public boolean isEnabled(Level level) { return isLoggable(logger, level); } }; } } // NOTE: assume that level enabled has been checked !!! public static void logMessage(Logger logger, Level level, Object message, Throwable t) { if ((logger == null) || (level == null) || Level.OFF.equals(level)) { return; } else if (Level.SEVERE.equals(level)) { logger.error(Objects.toString(message), t); } else if (Level.WARNING.equals(level)) { logger.warn(Objects.toString(message), t); } else if (Level.INFO.equals(level) || Level.ALL.equals(level)) { logger.info(Objects.toString(message), t); } else if (Level.CONFIG.equals(level) || Level.FINE.equals(level)) { logger.debug(Objects.toString(message), t); } else { logger.trace(Objects.toString(message), t); } } /** * @param logger The {@link Logger} instance - ignored if {@code null} * @param level The validate log {@link Level} - ignored if {@code null} * @return <P>{@code true} if the level is enabled for the logger. The * mapping of the level to the logger is as follows:</P> * <UL> * <LI>{@link Level#OFF} always returns {@code false}</LI> * <LI>{@link Level#SEVERE} returns {@link Logger#isErrorEnabled()}</LI> * <LI>{@link Level#WARNING} returns {@link Logger#isWarnEnabled()}</LI> * <LI>{@link Level#INFO} and {@link Level#ALL} returns {@link Logger#isInfoEnabled()}</LI> * <LI>{@link Level#CONFIG} and {@link Level#FINE} returns {@link Logger#isDebugEnabled()}</LI> * <LI>All other levels return {@link Logger#isTraceEnabled()}</LI> * </UL> */ public static boolean isLoggable(Logger logger, Level level) { if ((logger == null) || (level == null) || Level.OFF.equals(level)) { return false; } else if (Level.SEVERE.equals(level)) { return logger.isErrorEnabled(); } else if (Level.WARNING.equals(level)) { return logger.isWarnEnabled(); } else if (Level.INFO.equals(level) || Level.ALL.equals(level)) { return logger.isInfoEnabled(); } else if (Level.CONFIG.equals(level) || Level.FINE.equals(level)) { return logger.isDebugEnabled(); } else { return logger.isTraceEnabled(); } } }