一、前言
基于commons-logging源码包(1.0.3),对其中日志经常使用的org.apache.commons.logging.LogFactory抽象日志工厂类的实现类org.apache.commons.logging.impl.LogFactoryImpl工厂实现类进行源码分析,可以查出具体org.apache.commons.logging.log日志接口实现任基于org.apache.commons.logging.impl.Jdk14Logger、org.apache.commons.logging.impl.Log4JLogger日志实现类。
二、源码说明
1.LogFactory抽象类接口
package org.apache.commons.logging;@b@@b@import java.io.BufferedReader;@b@import java.io.IOException;@b@import java.io.InputStream;@b@import java.io.InputStreamReader;@b@import java.io.UnsupportedEncodingException;@b@import java.lang.reflect.InvocationTargetException;@b@import java.lang.reflect.Method;@b@import java.security.AccessController;@b@import java.security.PrivilegedAction;@b@import java.util.Enumeration;@b@import java.util.Hashtable;@b@import java.util.Properties;@b@@b@public abstract class LogFactory@b@{@b@ public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";@b@ public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl";@b@ public static final String FACTORY_PROPERTIES = "commons-logging.properties";@b@ protected static final String SERVICE_ID = "META-INF/services/org.apache.commons.logging.LogFactory";@b@ protected static Hashtable factories = new Hashtable();@b@@b@ public abstract void release();@b@@b@ public static void releaseAll()@b@ {@b@ synchronized (factories) {@b@ Enumeration elements = factories.elements();@b@ while (elements.hasMoreElements()) {@b@ LogFactory element = (LogFactory)elements.nextElement();@b@ element.release();@b@ }@b@ factories.clear();@b@ }@b@ }@b@@b@ protected static ClassLoader getContextClassLoader()@b@ throws LogConfigurationException@b@ {@b@ ClassLoader classLoader = null;@b@ try@b@ {@b@ Method method = Thread.class.getMethod("getContextClassLoader", null);@b@ try@b@ {@b@ classLoader = (ClassLoader)method.invoke(Thread.currentThread(), null);@b@ } catch (IllegalAccessException e) {@b@ throw new LogConfigurationException("Unexpected IllegalAccessException", e);@b@ }@b@ catch (InvocationTargetException e)@b@ {@b@ if (e.getTargetException() instanceof SecurityException) {@b@ break label85:@b@ }@b@@b@ throw new LogConfigurationException("Unexpected InvocationTargetException", e.getTargetException());@b@ }@b@@b@ label85: label88: break label88:@b@ }@b@ catch (NoSuchMethodException e) {@b@ classLoader = LogFactory.class.getClassLoader();@b@ }@b@@b@ return classLoader;@b@ }@b@@b@ public static void release(ClassLoader classLoader)@b@ {@b@ synchronized (factories) {@b@ LogFactory factory = (LogFactory)factories.get(classLoader);@b@ if (factory != null) {@b@ factory.release();@b@ factories.remove(classLoader);@b@ }@b@ }@b@ }@b@@b@ public abstract String[] getAttributeNames();@b@@b@ public abstract void removeAttribute(String paramString);@b@@b@ public static LogFactory getFactory()@b@ throws LogConfigurationException@b@ {@b@ String factoryClass;@b@ ClassLoader contextClassLoader = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()@b@ {@b@ public Object run()@b@ {@b@ return LogFactory.getContextClassLoader();@b@ }@b@@b@ });@b@ LogFactory factory = getCachedFactory(contextClassLoader);@b@ if (factory != null) {@b@ return factory;@b@ }@b@@b@ Properties props = null;@b@ try {@b@ InputStream stream = getResourceAsStream(contextClassLoader, "commons-logging.properties");@b@@b@ if (stream != null) {@b@ props = new Properties();@b@ props.load(stream);@b@ stream.close();@b@ }@b@ }@b@ catch (IOException e)@b@ {@b@ }@b@ catch (SecurityException e) {@b@ }@b@ try {@b@ factoryClass = System.getProperty("org.apache.commons.logging.LogFactory");@b@ if (factoryClass != null) {@b@ factory = newFactory(factoryClass, contextClassLoader);@b@ }@b@@b@ }@b@ catch (SecurityException e)@b@ {@b@ }@b@@b@ if (factory == null) {@b@ try {@b@ InputStream is = getResourceAsStream(contextClassLoader, "META-INF/services/org.apache.commons.logging.LogFactory");@b@@b@ if (is != null)@b@ {@b@ BufferedReader rd;@b@ try@b@ {@b@ rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));@b@ } catch (UnsupportedEncodingException e) {@b@ rd = new BufferedReader(new InputStreamReader(is));@b@ }@b@@b@ String factoryClassName = rd.readLine();@b@ rd.close();@b@@b@ if ((factoryClassName != null) && (!("".equals(factoryClassName))))@b@ {@b@ factory = newFactory(factoryClassName, contextClassLoader);@b@ }@b@@b@ }@b@@b@ }@b@ catch (Exception ex)@b@ {@b@ }@b@@b@ }@b@@b@ if ((factory == null) && (props != null)) {@b@ factoryClass = props.getProperty("org.apache.commons.logging.LogFactory");@b@ if (factoryClass != null) {@b@ factory = newFactory(factoryClass, contextClassLoader);@b@ }@b@@b@ }@b@@b@ if (factory == null) {@b@ factory = newFactory("org.apache.commons.logging.impl.LogFactoryImpl", LogFactory.class.getClassLoader());@b@ }@b@@b@ if (factory != null)@b@ {@b@ cacheFactory(contextClassLoader, factory);@b@@b@ if (props != null) {@b@ Enumeration names = props.propertyNames();@b@ while (names.hasMoreElements()) {@b@ String name = (String)names.nextElement();@b@ String value = props.getProperty(name);@b@ factory.setAttribute(name, value);@b@ }@b@ }@b@ }@b@@b@ return factory;@b@ }@b@@b@ public abstract Object getAttribute(String paramString);@b@@b@ public abstract void setAttribute(String paramString, Object paramObject);@b@@b@ public abstract Log getInstance(Class paramClass)@b@ throws LogConfigurationException;@b@@b@ public static Log getLog(Class clazz)@b@ throws LogConfigurationException@b@ {@b@ return getFactory().getInstance(clazz);@b@ }@b@@b@ public abstract Log getInstance(String paramString)@b@ throws LogConfigurationException;@b@@b@ public static Log getLog(String name)@b@ throws LogConfigurationException@b@ {@b@ return getFactory().getInstance(name);@b@ }@b@@b@ private static LogFactory getCachedFactory(ClassLoader contextClassLoader)@b@ {@b@ LogFactory factory = null;@b@@b@ if (contextClassLoader != null)@b@ factory = (LogFactory)factories.get(contextClassLoader);@b@@b@ return factory;@b@ }@b@@b@ private static void cacheFactory(ClassLoader classLoader, LogFactory factory)@b@ {@b@ if ((classLoader != null) && (factory != null))@b@ factories.put(classLoader, factory);@b@ }@b@@b@ private static InputStream getResourceAsStream(ClassLoader loader, String name)@b@ {@b@ return ((InputStream)AccessController.doPrivileged(new PrivilegedAction(loader, name) { private final ClassLoader val$loader;@b@ private final String val$name;@b@@b@ public Object run() { if (this.val$loader != null)@b@ return this.val$loader.getResourceAsStream(this.val$name);@b@@b@ return ClassLoader.getSystemResourceAsStream(this.val$name);@b@ }@b@ }));@b@ }@b@@b@ protected static LogFactory newFactory(String factoryClass, ClassLoader classLoader)@b@ throws LogConfigurationException@b@ {@b@ Object result = AccessController.doPrivileged(new PrivilegedAction(classLoader, factoryClass) {@b@ private final ClassLoader val$classLoader;@b@ private final String val$factoryClass;@b@@b@ public Object run() { try { if (this.val$classLoader != null)@b@ {@b@ try@b@ {@b@ return ((LogFactory)this.val$classLoader.loadClass(this.val$factoryClass).newInstance());@b@ } catch (ClassNotFoundException ex) {@b@ if (this.val$classLoader == ((LogFactory.class$org$apache$commons$logging$LogFactory == null) ? (LogFactory.class$org$apache$commons$logging$LogFactory = LogFactory.class$("org.apache.commons.logging.LogFactory")) : LogFactory.class$org$apache$commons$logging$LogFactory).getClassLoader())@b@ {@b@ throw ex;@b@ }@b@ }@b@ catch (NoClassDefFoundError e) {@b@ if (this.val$classLoader == ((LogFactory.class$org$apache$commons$logging$LogFactory == null) ? (LogFactory.class$org$apache$commons$logging$LogFactory = LogFactory.class$("org.apache.commons.logging.LogFactory")) : LogFactory.class$org$apache$commons$logging$LogFactory).getClassLoader())@b@ {@b@ throw e;@b@ }@b@ }@b@ catch (ClassCastException e)@b@ {@b@ if (this.val$classLoader == ((LogFactory.class$org$apache$commons$logging$LogFactory == null) ? (LogFactory.class$org$apache$commons$logging$LogFactory = LogFactory.class$("org.apache.commons.logging.LogFactory")) : LogFactory.class$org$apache$commons$logging$LogFactory).getClassLoader())@b@ {@b@ throw e;@b@ }@b@@b@ }@b@@b@ }@b@@b@ return ((LogFactory)Class.forName(this.val$factoryClass).newInstance()); } catch (Exception e) {@b@ }@b@ return new LogConfigurationException(e);@b@ }@b@@b@ });@b@ if (result instanceof LogConfigurationException)@b@ throw ((LogConfigurationException)result);@b@@b@ return ((LogFactory)result);@b@ }@b@}
2.实现工厂LogFactoryImpl类
package org.apache.commons.logging.impl;@b@@b@import java.lang.reflect.Constructor;@b@import java.lang.reflect.Method;@b@import java.security.AccessController;@b@import java.security.PrivilegedAction;@b@import java.util.Enumeration;@b@import java.util.Hashtable;@b@import java.util.Vector;@b@import org.apache.commons.logging.Log;@b@import org.apache.commons.logging.LogConfigurationException;@b@import org.apache.commons.logging.LogFactory;@b@@b@public class LogFactoryImpl extends LogFactory@b@{@b@ public static final String LOG_PROPERTY = "org.apache.commons.logging.Log";@b@ protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log";@b@ protected Hashtable attributes = new Hashtable();@b@ protected Hashtable instances = new Hashtable();@b@ private String logClassName;@b@ protected Constructor logConstructor = null;@b@ protected Class[] logConstructorSignature = { String.class };@b@ protected Method logMethod = null;@b@ protected Class[] logMethodSignature = { LogFactory.class };@b@@b@ public void release()@b@ {@b@ this.instances.clear();@b@ }@b@@b@ protected boolean isJdk14Available()@b@ {@b@ try@b@ {@b@ loadClass("java.util.logging.Logger");@b@ loadClass("org.apache.commons.logging.impl.Jdk14Logger");@b@ return true; } catch (Throwable t) {@b@ }@b@ return false;@b@ }@b@@b@ protected boolean isLog4JAvailable()@b@ {@b@ try@b@ {@b@ loadClass("org.apache.log4j.Logger");@b@ loadClass("org.apache.commons.logging.impl.Log4JLogger");@b@ return true; } catch (Throwable t) {@b@ }@b@ return false;@b@ }@b@@b@ static ClassLoader access$000()@b@ throws LogConfigurationException@b@ {@b@ return LogFactory.getContextClassLoader();@b@ }@b@@b@ protected String getLogClassName()@b@ {@b@ if (this.logClassName != null) {@b@ return this.logClassName;@b@ }@b@@b@ this.logClassName = ((String)getAttribute("org.apache.commons.logging.Log"));@b@@b@ if (this.logClassName == null) {@b@ this.logClassName = ((String)getAttribute("org.apache.commons.logging.log"));@b@ }@b@@b@ if (this.logClassName == null)@b@ try {@b@ this.logClassName = System.getProperty("org.apache.commons.logging.Log");@b@ }@b@ catch (SecurityException e)@b@ {@b@ }@b@@b@ if (this.logClassName == null)@b@ try {@b@ this.logClassName = System.getProperty("org.apache.commons.logging.log");@b@ }@b@ catch (SecurityException e)@b@ {@b@ }@b@@b@ if ((this.logClassName == null) && (isLog4JAvailable())) {@b@ this.logClassName = "org.apache.commons.logging.impl.Log4JLogger";@b@ }@b@@b@ if ((this.logClassName == null) && (isJdk14Available())) {@b@ this.logClassName = "org.apache.commons.logging.impl.Jdk14Logger";@b@ }@b@@b@ if (this.logClassName == null) {@b@ this.logClassName = "org.apache.commons.logging.impl.SimpleLog";@b@ }@b@@b@ return this.logClassName;@b@ }@b@@b@ public String[] getAttributeNames()@b@ {@b@ Vector names = new Vector();@b@ Enumeration keys = this.attributes.keys();@b@ while (keys.hasMoreElements())@b@ names.addElement((String)keys.nextElement());@b@@b@ String[] results = new String[names.size()];@b@ for (int i = 0; i < results.length; ++i)@b@ results[i] = ((String)names.elementAt(i));@b@@b@ return results;@b@ }@b@@b@ public void removeAttribute(String name)@b@ {@b@ this.attributes.remove(name);@b@ }@b@@b@ protected Constructor getLogConstructor()@b@ throws LogConfigurationException@b@ {@b@ if (this.logConstructor != null) {@b@ return this.logConstructor;@b@ }@b@@b@ String logClassName = getLogClassName();@b@@b@ Class logClass = null;@b@ try {@b@ logClass = loadClass(logClassName);@b@ if (logClass == null) {@b@ throw new LogConfigurationException("No suitable Log implementation for " + logClassName);@b@ }@b@@b@ if (!(Log.class.isAssignableFrom(logClass)))@b@ throw new LogConfigurationException("Class " + logClassName + " does not implement Log");@b@ }@b@ catch (Throwable t)@b@ {@b@ throw new LogConfigurationException(t);@b@ }@b@@b@ try@b@ {@b@ this.logMethod = logClass.getMethod("setLogFactory", this.logMethodSignature);@b@ }@b@ catch (Throwable t) {@b@ this.logMethod = null;@b@ }@b@@b@ try@b@ {@b@ this.logConstructor = logClass.getConstructor(this.logConstructorSignature);@b@ return this.logConstructor;@b@ } catch (Throwable t) {@b@ throw new LogConfigurationException("No suitable Log constructor " + this.logConstructorSignature + " for " + logClassName, t);@b@ }@b@ }@b@@b@ private static Class loadClass(String name)@b@ throws ClassNotFoundException@b@ {@b@ Object result = AccessController.doPrivileged(new PrivilegedAction(name) {@b@ private final String val$name;@b@@b@ public Object run() { ClassLoader threadCL = LogFactoryImpl.access$000();@b@ if (threadCL != null);@b@ try {@b@ return threadCL.loadClass(this.val$name);@b@ }@b@ catch (ClassNotFoundException e)@b@ {@b@ try@b@ {@b@ return Class.forName(this.val$name); } catch (ClassNotFoundException e) { }@b@ }@b@ return e;@b@ }@b@@b@ });@b@ if (result instanceof Class)@b@ return ((Class)result);@b@@b@ throw ((ClassNotFoundException)result);@b@ }@b@@b@ public Object getAttribute(String name)@b@ {@b@ return this.attributes.get(name);@b@ }@b@@b@ public void setAttribute(String name, Object value)@b@ {@b@ if (value == null)@b@ this.attributes.remove(name);@b@ else@b@ this.attributes.put(name, value);@b@ }@b@@b@ public Log getInstance(Class clazz)@b@ throws LogConfigurationException@b@ {@b@ return getInstance(clazz.getName());@b@ }@b@@b@ public Log getInstance(String name)@b@ throws LogConfigurationException@b@ {@b@ Log instance = (Log)this.instances.get(name);@b@ if (instance == null) {@b@ instance = newInstance(name);@b@ this.instances.put(name, instance);@b@ }@b@ return instance;@b@ }@b@@b@ protected Log newInstance(String name)@b@ throws LogConfigurationException@b@ {@b@ Log instance = null;@b@ try {@b@ Object[] params = new Object[1];@b@ params[0] = name;@b@ instance = (Log)getLogConstructor().newInstance(params);@b@ if (this.logMethod != null) {@b@ params[0] = this;@b@ this.logMethod.invoke(instance, params);@b@ }@b@ return instance;@b@ } catch (Throwable t) {@b@ throw new LogConfigurationException(t);@b@ }@b@ }@b@}
3.接口log
package org.apache.commons.logging;@b@@b@public abstract interface Log@b@{@b@ public abstract boolean isDebugEnabled();@b@@b@ public abstract boolean isErrorEnabled();@b@@b@ public abstract boolean isFatalEnabled();@b@@b@ public abstract boolean isInfoEnabled();@b@@b@ public abstract boolean isTraceEnabled();@b@@b@ public abstract boolean isWarnEnabled();@b@@b@ public abstract void debug(Object paramObject);@b@@b@ public abstract void error(Object paramObject);@b@@b@ public abstract void fatal(Object paramObject);@b@@b@ public abstract void info(Object paramObject);@b@@b@ public abstract void trace(Object paramObject);@b@@b@ public abstract void warn(Object paramObject);@b@@b@ public abstract void debug(Object paramObject, Throwable paramThrowable);@b@@b@ public abstract void error(Object paramObject, Throwable paramThrowable);@b@@b@ public abstract void fatal(Object paramObject, Throwable paramThrowable);@b@@b@ public abstract void info(Object paramObject, Throwable paramThrowable);@b@@b@ public abstract void trace(Object paramObject, Throwable paramThrowable);@b@@b@ public abstract void warn(Object paramObject, Throwable paramThrowable);@b@}
4.Jdk14Logger日志接口实现类
package org.apache.commons.logging.impl;@b@@b@import java.util.logging.Level;@b@import java.util.logging.Logger;@b@import org.apache.commons.logging.Log;@b@@b@public final class Jdk14Logger@b@ implements Log@b@{@b@ protected Logger logger = null;@b@@b@ public boolean isDebugEnabled()@b@ {@b@ return this.logger.isLoggable(Level.FINE);@b@ }@b@@b@ public boolean isErrorEnabled()@b@ {@b@ return this.logger.isLoggable(Level.SEVERE);@b@ }@b@@b@ public boolean isFatalEnabled()@b@ {@b@ return this.logger.isLoggable(Level.SEVERE);@b@ }@b@@b@ public boolean isInfoEnabled()@b@ {@b@ return this.logger.isLoggable(Level.INFO);@b@ }@b@@b@ public boolean isTraceEnabled()@b@ {@b@ return this.logger.isLoggable(Level.FINEST);@b@ }@b@@b@ public boolean isWarnEnabled()@b@ {@b@ return this.logger.isLoggable(Level.WARNING);@b@ }@b@@b@ public void debug(Object message)@b@ {@b@ log(Level.FINE, String.valueOf(message), null);@b@ }@b@@b@ public void error(Object message)@b@ {@b@ log(Level.SEVERE, String.valueOf(message), null);@b@ }@b@@b@ public void fatal(Object message)@b@ {@b@ log(Level.SEVERE, String.valueOf(message), null);@b@ }@b@@b@ public void info(Object message)@b@ {@b@ log(Level.INFO, String.valueOf(message), null);@b@ }@b@@b@ public void trace(Object message)@b@ {@b@ log(Level.FINEST, String.valueOf(message), null);@b@ }@b@@b@ public void warn(Object message)@b@ {@b@ log(Level.WARNING, String.valueOf(message), null);@b@ }@b@@b@ public Jdk14Logger(String name)@b@ {@b@ this.logger = Logger.getLogger(name);@b@ }@b@@b@ public Logger getLogger()@b@ {@b@ return this.logger;@b@ }@b@@b@ public void debug(Object message, Throwable exception)@b@ {@b@ log(Level.FINE, String.valueOf(message), exception);@b@ }@b@@b@ public void error(Object message, Throwable exception)@b@ {@b@ log(Level.SEVERE, String.valueOf(message), exception);@b@ }@b@@b@ public void fatal(Object message, Throwable exception)@b@ {@b@ log(Level.SEVERE, String.valueOf(message), exception);@b@ }@b@@b@ public void info(Object message, Throwable exception)@b@ {@b@ log(Level.INFO, String.valueOf(message), exception);@b@ }@b@@b@ public void trace(Object message, Throwable exception)@b@ {@b@ log(Level.FINEST, String.valueOf(message), exception);@b@ }@b@@b@ public void warn(Object message, Throwable exception)@b@ {@b@ log(Level.WARNING, String.valueOf(message), exception);@b@ }@b@@b@ private void log(Level level, String msg, Throwable ex)@b@ {@b@ if (this.logger.isLoggable(level))@b@ {@b@ Throwable dummyException = new Throwable();@b@ StackTraceElement[] locations = dummyException.getStackTrace();@b@@b@ String cname = "unknown";@b@ String method = "unknown";@b@ if ((locations != null) && (locations.length > 2)) {@b@ StackTraceElement caller = locations[2];@b@ cname = caller.getClassName();@b@ method = caller.getMethodName();@b@ }@b@ if (ex == null)@b@ this.logger.logp(level, cname, method, msg);@b@ else@b@ this.logger.logp(level, cname, method, msg, ex);@b@ }@b@ }@b@}
5.Log4JLogger日志接口实现类
package org.apache.commons.logging.impl;@b@@b@import org.apache.commons.logging.Log;@b@import org.apache.log4j.Logger;@b@import org.apache.log4j.Priority;@b@@b@public final class Log4JLogger@b@ implements Log@b@{@b@ private static final String FQCN = Log4JLogger.class.getName();@b@ private Logger logger = null;@b@@b@ public Log4JLogger()@b@ {@b@ }@b@@b@ public boolean isDebugEnabled()@b@ {@b@ return this.logger.isDebugEnabled();@b@ }@b@@b@ public boolean isErrorEnabled()@b@ {@b@ return this.logger.isEnabledFor(Priority.ERROR);@b@ }@b@@b@ public boolean isFatalEnabled()@b@ {@b@ return this.logger.isEnabledFor(Priority.FATAL);@b@ }@b@@b@ public boolean isInfoEnabled()@b@ {@b@ return this.logger.isInfoEnabled();@b@ }@b@@b@ public boolean isTraceEnabled()@b@ {@b@ return this.logger.isDebugEnabled();@b@ }@b@@b@ public boolean isWarnEnabled()@b@ {@b@ return this.logger.isEnabledFor(Priority.WARN);@b@ }@b@@b@ public void debug(Object message)@b@ {@b@ this.logger.log(FQCN, Priority.DEBUG, message, null);@b@ }@b@@b@ public void error(Object message)@b@ {@b@ this.logger.log(FQCN, Priority.ERROR, message, null);@b@ }@b@@b@ public void fatal(Object message)@b@ {@b@ this.logger.log(FQCN, Priority.FATAL, message, null);@b@ }@b@@b@ public void info(Object message)@b@ {@b@ this.logger.log(FQCN, Priority.INFO, message, null);@b@ }@b@@b@ public void trace(Object message)@b@ {@b@ this.logger.log(FQCN, Priority.DEBUG, message, null);@b@ }@b@@b@ public void warn(Object message)@b@ {@b@ this.logger.log(FQCN, Priority.WARN, message, null);@b@ }@b@@b@ public Log4JLogger(String name)@b@ {@b@ this.logger = Logger.getLogger(name);@b@ }@b@@b@ public Logger getLogger()@b@ {@b@ return this.logger;@b@ }@b@@b@ public Log4JLogger(Logger logger)@b@ {@b@ this.logger = logger;@b@ }@b@@b@ public void debug(Object message, Throwable t)@b@ {@b@ this.logger.log(FQCN, Priority.DEBUG, message, t);@b@ }@b@@b@ public void error(Object message, Throwable t)@b@ {@b@ this.logger.log(FQCN, Priority.ERROR, message, t);@b@ }@b@@b@ public void fatal(Object message, Throwable t)@b@ {@b@ this.logger.log(FQCN, Priority.FATAL, message, t);@b@ }@b@@b@ public void info(Object message, Throwable t)@b@ {@b@ this.logger.log(FQCN, Priority.INFO, message, t);@b@ }@b@@b@ public void trace(Object message, Throwable t)@b@ {@b@ this.logger.log(FQCN, Priority.DEBUG, message, t);@b@ }@b@@b@ public void warn(Object message, Throwable t)@b@ {@b@ this.logger.log(FQCN, Priority.WARN, message, t);@b@ }@b@}