mirror of
https://gitee.com/replugin/RePlugin.git
synced 2024-11-30 02:38:34 +08:00
Use RePlugin's own reflection library to replace Apache Lang3
This commit is contained in:
parent
d3ef61a51a
commit
ed2c5dceac
@ -23,11 +23,7 @@ import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.qihoo360.replugin.helper.LogDebug;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.ConstructorUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.FieldUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.MethodUtils;
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -47,13 +43,15 @@ public class PackageUtils {
|
||||
//
|
||||
try {
|
||||
// 1. 新建PackageParser的实例
|
||||
Object packageParser = ConstructorUtils.invokeConstructor(ClassUtils.getClass("android.content.pm.PackageParser"), archiveFilePath);
|
||||
Object packageParser = ReflectUtils.invokeConstructor(ReflectUtils.getClass("android.content.pm.PackageParser"),
|
||||
new Class[]{String.class}, archiveFilePath);
|
||||
|
||||
// 2. 调用PackageParser.parsePackage()方法,返回值为Package对象
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
metrics.setToDefaults();
|
||||
|
||||
Object pkg = MethodUtils.invokeMethod(packageParser, true, "parsePackage", new File(archiveFilePath), archiveFilePath, metrics, 0);
|
||||
Object pkg = ReflectUtils.invokeMethod(packageParser, "parsePackage", new Class[]{File.class, String.class, DisplayMetrics.class, int.class},
|
||||
new File(archiveFilePath), archiveFilePath, metrics, 0);
|
||||
if (pkg == null) {
|
||||
if (LOG) {
|
||||
LogDebug.d(MISC_TAG, "failed to parsePackage: f=" + archiveFilePath);
|
||||
@ -62,13 +60,14 @@ public class PackageUtils {
|
||||
}
|
||||
|
||||
// 3. 调用PackageParser.collectCertificates方法
|
||||
boolean rc = (Boolean) MethodUtils.invokeMethod(packageParser, "collectCertificates", pkg, 0);
|
||||
boolean rc = (Boolean) ReflectUtils.invokeMethod(packageParser, "collectCertificates", new Class[]{pkg.getClass(), int.class},
|
||||
pkg, 0);
|
||||
if (!rc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 获取Package.mSignatures
|
||||
Object signatures[] = (Object[]) FieldUtils.readField(pkg, "mSignatures");
|
||||
Object signatures[] = (Object[]) ReflectUtils.readField(pkg, "mSignatures");
|
||||
int n = signatures.length;
|
||||
if (n <= 0) {
|
||||
if (LOG) {
|
||||
|
@ -22,8 +22,7 @@ import android.util.Log;
|
||||
|
||||
import com.qihoo360.replugin.RePlugin;
|
||||
import com.qihoo360.replugin.helper.LogRelease;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.FieldUtils;
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
|
||||
import static com.qihoo360.replugin.helper.LogDebug.LOG;
|
||||
import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG;
|
||||
@ -53,7 +52,7 @@ public class PatchClassLoaderUtils {
|
||||
// 1. ApplicationContext - Android 2.1
|
||||
// 2. ContextImpl - Android 2.2 and higher
|
||||
// 3. AppContextImpl - Android 2.2 and higher
|
||||
Object oPackageInfo = FieldUtils.readField(oBase, "mPackageInfo", true);
|
||||
Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
|
||||
if (oPackageInfo == null) {
|
||||
if (LOGR) {
|
||||
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass());
|
||||
@ -69,7 +68,7 @@ public class PatchClassLoaderUtils {
|
||||
}
|
||||
|
||||
// 获取mPackageInfo.mClassLoader
|
||||
ClassLoader oClassLoader = (ClassLoader) FieldUtils.readField(oPackageInfo, "mClassLoader", true);
|
||||
ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
|
||||
if (oClassLoader == null) {
|
||||
if (LOGR) {
|
||||
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass());
|
||||
@ -81,7 +80,7 @@ public class PatchClassLoaderUtils {
|
||||
ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);
|
||||
|
||||
// 将新的ClassLoader写入mPackageInfo.mClassLoader
|
||||
FieldUtils.writeField(oPackageInfo, "mClassLoader", cl, true);
|
||||
ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);
|
||||
|
||||
if (LOG) {
|
||||
Log.d(TAG, "patch: patch mClassLoader ok");
|
||||
|
@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2017 Qihoo 360 Inc.
|
||||
*
|
||||
* Licensed 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 com.qihoo360.loader.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.qihoo360.replugin.helper.LogRelease;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static com.qihoo360.replugin.helper.LogDebug.MISC_TAG;
|
||||
import static com.qihoo360.replugin.helper.LogRelease.LOGR;
|
||||
|
||||
/**
|
||||
* @author RePlugin Team
|
||||
*/
|
||||
public final class ReflectUtils {
|
||||
|
||||
public static final void setFieldNonE(Class<?> c, Object object, String fName, Object value) {
|
||||
try {
|
||||
setField(c, object, fName, value);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static final void setField(Class<?> c, Object object, String fName, Object value) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field f = c.getDeclaredField(fName);
|
||||
boolean acc = f.isAccessible();
|
||||
if (!acc) {
|
||||
f.setAccessible(true);
|
||||
}
|
||||
f.set(object, value);
|
||||
if (!acc) {
|
||||
f.setAccessible(acc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final void dumpObject(Object object, FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
try {
|
||||
Class<?> c = object.getClass();
|
||||
do {
|
||||
writer.println("c=" + c.getName());
|
||||
Field fields[] = c.getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
boolean acc = f.isAccessible();
|
||||
if (!acc) {
|
||||
f.setAccessible(true);
|
||||
}
|
||||
Object o = f.get(object);
|
||||
writer.print(f.getName());
|
||||
writer.print("=");
|
||||
if (o != null) {
|
||||
writer.println(o.toString());
|
||||
} else {
|
||||
writer.println("null");
|
||||
}
|
||||
if (!acc) {
|
||||
f.setAccessible(acc);
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
} while (c != null && !c.equals(Object.class) && !c.equals(Context.class));
|
||||
} catch (Throwable e) {
|
||||
if (LOGR) {
|
||||
LogRelease.e(MISC_TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Object invokeMethod(ClassLoader loader, String clzName,
|
||||
String methodName, Object methodReceiver,
|
||||
Class<?>[] methodParamTypes, Object... methodParamValues) throws
|
||||
ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
if (methodReceiver == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class clz = Class.forName(clzName, false, loader);
|
||||
if (clz != null) {
|
||||
Method med = clz.getMethod(methodName, methodParamTypes);
|
||||
if (med != null) {
|
||||
med.setAccessible(true);
|
||||
return med.invoke(methodReceiver, methodParamValues);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -16,8 +16,7 @@
|
||||
|
||||
package com.qihoo360.loader.utils2;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.MethodUtils;
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@ -58,7 +57,7 @@ public class FilePermissionUtils {
|
||||
try {
|
||||
initClass();
|
||||
if (sSetPermissionMethod == null) {
|
||||
sSetPermissionMethod = MethodUtils.getMatchingMethod(sFileUtilsClass, "setPermissions",
|
||||
sSetPermissionMethod = ReflectUtils.getMethod(sFileUtilsClass, "setPermissions",
|
||||
String.class, Integer.TYPE, Integer.TYPE, Integer.TYPE);
|
||||
}
|
||||
Object retObj = sSetPermissionMethod.invoke(null, filePath, mode, uid, gid);
|
||||
@ -85,7 +84,7 @@ public class FilePermissionUtils {
|
||||
try {
|
||||
initClass();
|
||||
if (sGetPermissionMethod == null) {
|
||||
sGetPermissionMethod = MethodUtils.getMatchingMethod(sFileUtilsClass, "getPermissions",
|
||||
sGetPermissionMethod = ReflectUtils.getMethod(sFileUtilsClass, "getPermissions",
|
||||
String.class, int[].class);
|
||||
}
|
||||
Object retObj = sGetPermissionMethod.invoke(null, filePath, outPermissions);
|
||||
@ -104,7 +103,7 @@ public class FilePermissionUtils {
|
||||
|
||||
private static void initClass() throws ClassNotFoundException {
|
||||
if (sFileUtilsClass == null) {
|
||||
sFileUtilsClass = ClassUtils.getClass("android.os.FileUtils");
|
||||
sFileUtilsClass = ReflectUtils.getClass("android.os.FileUtils");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,8 @@ import com.qihoo360.i.Factory;
|
||||
import com.qihoo360.i.IModule;
|
||||
import com.qihoo360.i.IPluginActivityManager;
|
||||
import com.qihoo360.i.IPluginManager;
|
||||
import com.qihoo360.loader.utils.ReflectUtils;
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
import com.qihoo360.mobilesafe.api.Tasks;
|
||||
import com.qihoo360.mobilesafe.core.BuildConfig;
|
||||
import com.qihoo360.replugin.IHostBinderFetcher;
|
||||
import com.qihoo360.replugin.RePlugin;
|
||||
import com.qihoo360.replugin.RePluginConstants;
|
||||
|
@ -29,12 +29,10 @@ import com.qihoo360.i.Factory;
|
||||
import com.qihoo360.i.Factory2;
|
||||
import com.qihoo360.i.IPluginActivityManager;
|
||||
import com.qihoo360.i.IPluginManager;
|
||||
import com.qihoo360.loader.utils.ReflectUtils;
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
import com.qihoo360.replugin.RePlugin;
|
||||
import com.qihoo360.replugin.base.IPC;
|
||||
import com.qihoo360.replugin.component.activity.ActivityInjector;
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.FieldUtils;
|
||||
import com.qihoo360.replugin.helper.HostConfigHelper;
|
||||
import com.qihoo360.replugin.helper.LogDebug;
|
||||
import com.qihoo360.replugin.helper.LogRelease;
|
||||
@ -680,12 +678,14 @@ class PmInternalImpl implements IPluginActivityManager {
|
||||
private static int getDefaultThemeId() {
|
||||
if (HostConfigHelper.ACTIVITY_PIT_USE_APPCOMPAT) {
|
||||
try {
|
||||
Class clazz = ClassUtils.getClass("android.support.v7.appcompat.R$style");
|
||||
return (int) FieldUtils.readStaticField(clazz, "Theme_AppCompat", true);
|
||||
Class clazz = ReflectUtils.getClass("android.support.v7.appcompat.R$style");
|
||||
return (int) ReflectUtils.readStaticField(clazz, "Theme_AppCompat");
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return android.R.style.Theme_NoTitleBar;
|
||||
|
@ -16,10 +16,9 @@
|
||||
|
||||
package com.qihoo360.replugin;
|
||||
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
import com.qihoo360.replugin.helper.LogDebug;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.MethodUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@ -63,7 +62,7 @@ public class PluginDexClassLoader extends DexClassLoader {
|
||||
private static void initMethods(ClassLoader cl) {
|
||||
Class<?> clz = cl.getClass();
|
||||
if (sLoadClassMethod == null) {
|
||||
sLoadClassMethod = MethodUtils.getMatchingMethod(clz, "loadClass", String.class, Boolean.TYPE);
|
||||
sLoadClassMethod = ReflectUtils.getMethod(clz, "loadClass", String.class, Boolean.TYPE);
|
||||
if (sLoadClassMethod == null) {
|
||||
throw new NoSuchMethodError("loadClass");
|
||||
}
|
||||
|
@ -18,15 +18,13 @@ package com.qihoo360.replugin;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
import com.qihoo360.loader.utils.StringUtils;
|
||||
import com.qihoo360.loader2.PMF;
|
||||
import com.qihoo360.replugin.base.IPC;
|
||||
import com.qihoo360.replugin.helper.LogDebug;
|
||||
import com.qihoo360.replugin.helper.LogRelease;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.FieldUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.MethodUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@ -83,19 +81,19 @@ public class RePluginClassLoader extends PathClassLoader {
|
||||
|
||||
private void initMethods(ClassLoader cl) {
|
||||
Class<?> c = cl.getClass();
|
||||
findResourceMethod = MethodUtils.getMatchingMethod(c, "findResource", String.class);
|
||||
findResourceMethod = ReflectUtils.getMethod(c, "findResource", String.class);
|
||||
findResourceMethod.setAccessible(true);
|
||||
findResourcesMethod = MethodUtils.getMatchingMethod(c, "findResources", String.class);
|
||||
findResourcesMethod = ReflectUtils.getMethod(c, "findResources", String.class);
|
||||
findResourcesMethod.setAccessible(true);
|
||||
findLibraryMethod = MethodUtils.getMatchingMethod(c, "findLibrary", String.class);
|
||||
findLibraryMethod = ReflectUtils.getMethod(c, "findLibrary", String.class);
|
||||
findLibraryMethod.setAccessible(true);
|
||||
getPackageMethod = MethodUtils.getMatchingMethod(c, "getPackage", String.class);
|
||||
getPackageMethod = ReflectUtils.getMethod(c, "getPackage", String.class);
|
||||
getPackageMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
private void copyFromOriginal(ClassLoader orig) {
|
||||
if (LOG && IPC.isPersistentProcess()) {
|
||||
LogDebug.d(TAG, "copyFromOriginal: Fields=" + StringUtils.toStringWithLines(FieldUtils.getAllFieldsList(orig.getClass())));
|
||||
LogDebug.d(TAG, "copyFromOriginal: Fields=" + StringUtils.toStringWithLines(ReflectUtils.getAllFieldsList(orig.getClass())));
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
|
||||
@ -116,7 +114,7 @@ public class RePluginClassLoader extends PathClassLoader {
|
||||
|
||||
private void copyFieldValue(String field, ClassLoader orig) {
|
||||
try {
|
||||
Field f = FieldUtils.getField(orig.getClass(), field, true);
|
||||
Field f = ReflectUtils.getField(orig.getClass(), field);
|
||||
if (f == null) {
|
||||
if (LOGR) {
|
||||
LogRelease.e(PLUGIN_TAG, "rpcl.cfv: null! f=" + field);
|
||||
@ -125,14 +123,14 @@ public class RePluginClassLoader extends PathClassLoader {
|
||||
}
|
||||
|
||||
// 删除final修饰符
|
||||
FieldUtils.removeFinalModifier(f);
|
||||
ReflectUtils.removeFieldFinalModifier(f);
|
||||
|
||||
// 复制Field中的值到this里
|
||||
Object o = FieldUtils.readField(f, orig);
|
||||
FieldUtils.writeField(f, this, o);
|
||||
Object o = ReflectUtils.readField(f, orig);
|
||||
ReflectUtils.writeField(f, this, o);
|
||||
|
||||
if (LOG) {
|
||||
Object test = FieldUtils.readField(f, this);
|
||||
Object test = ReflectUtils.readField(f, this);
|
||||
LogDebug.d(TAG, "copyFieldValue: Copied. f=" + field + "; actually=" + test + "; orig=" + o);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,306 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3;
|
||||
|
||||
/**
|
||||
* <p>Operations on {@link CharSequence} that are
|
||||
* {@code null} safe.</p>
|
||||
*
|
||||
* @see CharSequence
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CharSequenceUtils {
|
||||
|
||||
private static final int NOT_FOUND = -1;
|
||||
|
||||
/**
|
||||
* <p>{@code CharSequenceUtils} instances should NOT be constructed in
|
||||
* standard programming. </p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public CharSequenceUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns a new {@code CharSequence} that is a subsequence of this
|
||||
* sequence starting with the {@code char} value at the specified index.</p>
|
||||
*
|
||||
* <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
|
||||
* The length (in {@code char}) of the returned sequence is {@code length() - start},
|
||||
* so if {@code start == end} then an empty sequence is returned.</p>
|
||||
*
|
||||
* @param cs the specified subsequence, null returns null
|
||||
* @param start the start index, inclusive, valid
|
||||
* @return a new subsequence, may be null
|
||||
* @throws IndexOutOfBoundsException if {@code start} is negative or if
|
||||
* {@code start} is greater than {@code length()}
|
||||
*/
|
||||
public static CharSequence subSequence(final CharSequence cs, final int start) {
|
||||
return cs == null ? null : cs.subSequence(start, cs.length());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the index within <code>cs</code> of the first occurrence of the
|
||||
* specified character, starting the search at the specified index.
|
||||
* <p>
|
||||
* If a character with value <code>searchChar</code> occurs in the
|
||||
* character sequence represented by the <code>cs</code>
|
||||
* object at an index no smaller than <code>start</code>, then
|
||||
* the index of the first such occurrence is returned. For values
|
||||
* of <code>searchChar</code> in the range from 0 to 0xFFFF (inclusive),
|
||||
* this is the smallest value <i>k</i> such that:
|
||||
* <blockquote><pre>
|
||||
* (this.charAt(<i>k</i>) == searchChar) && (<i>k</i> >= start)
|
||||
* </pre></blockquote>
|
||||
* is true. For other values of <code>searchChar</code>, it is the
|
||||
* smallest value <i>k</i> such that:
|
||||
* <blockquote><pre>
|
||||
* (this.codePointAt(<i>k</i>) == searchChar) && (<i>k</i> >= start)
|
||||
* </pre></blockquote>
|
||||
* is true. In either case, if no such character occurs inm <code>cs</code>
|
||||
* at or after position <code>start</code>, then
|
||||
* <code>-1</code> is returned.
|
||||
*
|
||||
* <p>
|
||||
* There is no restriction on the value of <code>start</code>. If it
|
||||
* is negative, it has the same effect as if it were zero: the entire
|
||||
* <code>CharSequence</code> may be searched. If it is greater than
|
||||
* the length of <code>cs</code>, it has the same effect as if it were
|
||||
* equal to the length of <code>cs</code>: <code>-1</code> is returned.
|
||||
*
|
||||
* <p>All indices are specified in <code>char</code> values
|
||||
* (Unicode code units).
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed, not null
|
||||
* @param searchChar the char to be searched for
|
||||
* @param start the start index, negative starts at the string start
|
||||
* @return the index where the search char was found, -1 if not found
|
||||
* @since 3.6 updated to behave more like <code>String</code>
|
||||
*/
|
||||
static int indexOf(final CharSequence cs, final int searchChar, int start) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).indexOf(searchChar, start);
|
||||
}
|
||||
final int sz = cs.length();
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
|
||||
for (int i = start; i < sz; i++) {
|
||||
if (cs.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
//supplementary characters (LANG1300)
|
||||
if (searchChar <= Character.MAX_CODE_POINT) {
|
||||
char[] chars = Character.toChars(searchChar);
|
||||
for (int i = start; i < sz - 1; i++) {
|
||||
char high = cs.charAt(i);
|
||||
char low = cs.charAt(i + 1);
|
||||
if (high == chars[0] && low == chars[1]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the {@code CharSequence} to be searched for
|
||||
* @param start the start index
|
||||
* @return the index where the search sequence was found
|
||||
*/
|
||||
static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
|
||||
return cs.toString().indexOf(searchChar.toString(), start);
|
||||
// if (cs instanceof String && searchChar instanceof String) {
|
||||
// // TODO: Do we assume searchChar is usually relatively small;
|
||||
// // If so then calling toString() on it is better than reverting to
|
||||
// // the green implementation in the else block
|
||||
// return ((String) cs).indexOf((String) searchChar, start);
|
||||
// } else {
|
||||
// // TODO: Implement rather than convert to String
|
||||
// return cs.toString().indexOf(searchChar.toString(), start);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within <code>cs</code> of the last occurrence of
|
||||
* the specified character, searching backward starting at the
|
||||
* specified index. For values of <code>searchChar</code> in the range
|
||||
* from 0 to 0xFFFF (inclusive), the index returned is the largest
|
||||
* value <i>k</i> such that:
|
||||
* <blockquote><pre>
|
||||
* (this.charAt(<i>k</i>) == searchChar) && (<i>k</i> <= start)
|
||||
* </pre></blockquote>
|
||||
* is true. For other values of <code>searchChar</code>, it is the
|
||||
* largest value <i>k</i> such that:
|
||||
* <blockquote><pre>
|
||||
* (this.codePointAt(<i>k</i>) == searchChar) && (<i>k</i> <= start)
|
||||
* </pre></blockquote>
|
||||
* is true. In either case, if no such character occurs in <code>cs</code>
|
||||
* at or before position <code>start</code>, then <code>-1</code> is returned.
|
||||
*
|
||||
* <p>All indices are specified in <code>char</code> values
|
||||
* (Unicode code units).
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the char to be searched for
|
||||
* @param start the start index, negative returns -1, beyond length starts at end
|
||||
* @return the index where the search char was found, -1 if not found
|
||||
* @since 3.6 updated to behave more like <code>String</code>
|
||||
*/
|
||||
static int lastIndexOf(final CharSequence cs, final int searchChar, int start) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).lastIndexOf(searchChar, start);
|
||||
}
|
||||
final int sz = cs.length();
|
||||
if (start < 0) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
if (start >= sz) {
|
||||
start = sz - 1;
|
||||
}
|
||||
if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
|
||||
for (int i = start; i >= 0; --i) {
|
||||
if (cs.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
//supplementary characters (LANG1300)
|
||||
//NOTE - we must do a forward traversal for this to avoid duplicating code points
|
||||
if (searchChar <= Character.MAX_CODE_POINT) {
|
||||
char[] chars = Character.toChars(searchChar);
|
||||
//make sure it's not the last index
|
||||
if (start == sz - 1) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
for (int i = start; i >= 0; i--) {
|
||||
char high = cs.charAt(i);
|
||||
char low = cs.charAt(i + 1);
|
||||
if (chars[0] == high && chars[1] == low) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the {@code CharSequence} to be searched for
|
||||
* @param start the start index
|
||||
* @return the index where the search sequence was found
|
||||
*/
|
||||
static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
|
||||
return cs.toString().lastIndexOf(searchChar.toString(), start);
|
||||
// if (cs instanceof String && searchChar instanceof String) {
|
||||
// // TODO: Do we assume searchChar is usually relatively small;
|
||||
// // If so then calling toString() on it is better than reverting to
|
||||
// // the green implementation in the else block
|
||||
// return ((String) cs).lastIndexOf((String) searchChar, start);
|
||||
// } else {
|
||||
// // TODO: Implement rather than convert to String
|
||||
// return cs.toString().lastIndexOf(searchChar.toString(), start);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Green implementation of toCharArray.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @return the resulting char array
|
||||
*/
|
||||
static char[] toCharArray(final CharSequence cs) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).toCharArray();
|
||||
}
|
||||
final int sz = cs.length();
|
||||
final char[] array = new char[cs.length()];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
array[i] = cs.charAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Green implementation of regionMatches.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param ignoreCase whether or not to be case insensitive
|
||||
* @param thisStart the index to start on the {@code cs} CharSequence
|
||||
* @param substring the {@code CharSequence} to be looked for
|
||||
* @param start the index to start on the {@code substring} CharSequence
|
||||
* @param length character length of the region
|
||||
* @return whether the region matched
|
||||
*/
|
||||
static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
|
||||
final CharSequence substring, final int start, final int length) {
|
||||
if (cs instanceof String && substring instanceof String) {
|
||||
return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
|
||||
}
|
||||
int index1 = thisStart;
|
||||
int index2 = start;
|
||||
int tmpLen = length;
|
||||
|
||||
// Extract these first so we detect NPEs the same as the java.lang.String version
|
||||
final int srcLen = cs.length() - thisStart;
|
||||
final int otherLen = substring.length() - start;
|
||||
|
||||
// Check for invalid parameters
|
||||
if (thisStart < 0 || start < 0 || length < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the regions are long enough
|
||||
if (srcLen < length || otherLen < length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (tmpLen-- > 0) {
|
||||
final char c1 = cs.charAt(index1++);
|
||||
final char c2 = substring.charAt(index2++);
|
||||
|
||||
if (c1 == c2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ignoreCase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The same check as in String.regionMatches():
|
||||
if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
|
||||
&& Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,552 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3;
|
||||
|
||||
/**
|
||||
* <p>Operations on char primitives and Character objects.</p>
|
||||
*
|
||||
* <p>This class tries to handle {@code null} input gracefully.
|
||||
* An exception will not be thrown for a {@code null} input.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* <p>#ThreadSafe#</p>
|
||||
* @since 2.1
|
||||
*/
|
||||
public class CharUtils {
|
||||
|
||||
private static final String[] CHAR_STRING_ARRAY = new String[128];
|
||||
|
||||
private static final char[] HEX_DIGITS = new char[] {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
|
||||
|
||||
/**
|
||||
* {@code \u000a} linefeed LF ('\n').
|
||||
*
|
||||
* @see <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
|
||||
* for Character and String Literals</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static final char LF = '\n';
|
||||
|
||||
/**
|
||||
* {@code \u000d} carriage return CR ('\r').
|
||||
*
|
||||
* @see <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
|
||||
* for Character and String Literals</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static final char CR = '\r';
|
||||
|
||||
/**
|
||||
* {@code \u0000} null control character ('\0'), abbreviated NUL.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public static final char NUL = '\0';
|
||||
|
||||
static {
|
||||
for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) {
|
||||
CHAR_STRING_ARRAY[c] = String.valueOf(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>{@code CharUtils} instances should NOT be constructed in standard programming.
|
||||
* Instead, the class should be used as {@code CharUtils.toString('c');}.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean instance
|
||||
* to operate.</p>
|
||||
*/
|
||||
public CharUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to a Character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same Character object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toCharacterObject(' ') = ' '
|
||||
* CharUtils.toCharacterObject('A') = 'A'
|
||||
* </pre>
|
||||
*
|
||||
* @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127.
|
||||
* @param ch the character to convert
|
||||
* @return a Character of the specified character
|
||||
*/
|
||||
@Deprecated
|
||||
public static Character toCharacterObject(final char ch) {
|
||||
return Character.valueOf(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the String to a Character using the first character, returning
|
||||
* null for empty Strings.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same Character object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toCharacterObject(null) = null
|
||||
* CharUtils.toCharacterObject("") = null
|
||||
* CharUtils.toCharacterObject("A") = 'A'
|
||||
* CharUtils.toCharacterObject("BA") = 'B'
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @return the Character value of the first letter of the String
|
||||
*/
|
||||
public static Character toCharacterObject(final String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return null;
|
||||
}
|
||||
return Character.valueOf(str.charAt(0));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the Character to a char throwing an exception for {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(' ') = ' '
|
||||
* CharUtils.toChar('A') = 'A'
|
||||
* CharUtils.toChar(null) throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the char value of the Character
|
||||
* @throws IllegalArgumentException if the Character is null
|
||||
*/
|
||||
public static char toChar(final Character ch) {
|
||||
Validate.isTrue(ch != null, "The Character must not be null");
|
||||
return ch.charValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the Character to a char handling {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(null, 'X') = 'X'
|
||||
* CharUtils.toChar(' ', 'X') = ' '
|
||||
* CharUtils.toChar('A', 'X') = 'A'
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the value to use if the Character is null
|
||||
* @return the char value of the Character or the default if null
|
||||
*/
|
||||
public static char toChar(final Character ch, final char defaultValue) {
|
||||
if (ch == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ch.charValue();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the String to a char using the first character, throwing
|
||||
* an exception on empty Strings.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar("A") = 'A'
|
||||
* CharUtils.toChar("BA") = 'B'
|
||||
* CharUtils.toChar(null) throws IllegalArgumentException
|
||||
* CharUtils.toChar("") throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @return the char value of the first letter of the String
|
||||
* @throws IllegalArgumentException if the String is empty
|
||||
*/
|
||||
public static char toChar(final String str) {
|
||||
Validate.isTrue(StringUtils.isNotEmpty(str), "The String must not be empty");
|
||||
return str.charAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the String to a char using the first character, defaulting
|
||||
* the value on empty Strings.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(null, 'X') = 'X'
|
||||
* CharUtils.toChar("", 'X') = 'X'
|
||||
* CharUtils.toChar("A", 'X') = 'A'
|
||||
* CharUtils.toChar("BA", 'X') = 'B'
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @param defaultValue the value to use if the Character is null
|
||||
* @return the char value of the first letter of the String or the default if null
|
||||
*/
|
||||
public static char toChar(final String str, final char defaultValue) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return str.charAt(0);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3') = 3
|
||||
* CharUtils.toIntValue('A') throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the int value of the character
|
||||
* @throws IllegalArgumentException if the character is not ASCII numeric
|
||||
*/
|
||||
public static int toIntValue(final char ch) {
|
||||
if (isAsciiNumeric(ch) == false) {
|
||||
throw new IllegalArgumentException("The character " + ch + " is not in the range '0' - '9'");
|
||||
}
|
||||
return ch - 48;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3', -1) = 3
|
||||
* CharUtils.toIntValue('A', -1) = -1
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the default value to use if the character is not numeric
|
||||
* @return the int value of the character
|
||||
*/
|
||||
public static int toIntValue(final char ch, final int defaultValue) {
|
||||
if (isAsciiNumeric(ch) == false) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ch - 48;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3') = 3
|
||||
* CharUtils.toIntValue(null) throws IllegalArgumentException
|
||||
* CharUtils.toIntValue('A') throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert, not null
|
||||
* @return the int value of the character
|
||||
* @throws IllegalArgumentException if the Character is not ASCII numeric or is null
|
||||
*/
|
||||
public static int toIntValue(final Character ch) {
|
||||
Validate.isTrue(ch != null, "The character must not be null");
|
||||
return toIntValue(ch.charValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue(null, -1) = -1
|
||||
* CharUtils.toIntValue('3', -1) = 3
|
||||
* CharUtils.toIntValue('A', -1) = -1
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the default value to use if the character is not numeric
|
||||
* @return the int value of the character
|
||||
*/
|
||||
public static int toIntValue(final Character ch, final int defaultValue) {
|
||||
if (ch == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return toIntValue(ch.charValue(), defaultValue);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to a String that contains the one character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same String object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toString(' ') = " "
|
||||
* CharUtils.toString('A') = "A"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return a String containing the one specified character
|
||||
*/
|
||||
public static String toString(final char ch) {
|
||||
if (ch < 128) {
|
||||
return CHAR_STRING_ARRAY[ch];
|
||||
}
|
||||
return new String(new char[] {ch});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to a String that contains the one character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same String object each time.</p>
|
||||
*
|
||||
* <p>If {@code null} is passed in, {@code null} will be returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toString(null) = null
|
||||
* CharUtils.toString(' ') = " "
|
||||
* CharUtils.toString('A') = "A"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return a String containing the one specified character
|
||||
*/
|
||||
public static String toString(final Character ch) {
|
||||
if (ch == null) {
|
||||
return null;
|
||||
}
|
||||
return toString(ch.charValue());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the string to the Unicode format '\u0020'.</p>
|
||||
*
|
||||
* <p>This format is the Java source code format.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.unicodeEscaped(' ') = "\u0020"
|
||||
* CharUtils.unicodeEscaped('A') = "\u0041"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the escaped Unicode string
|
||||
*/
|
||||
public static String unicodeEscaped(final char ch) {
|
||||
final StringBuilder sb = new StringBuilder(6);
|
||||
sb.append("\\u");
|
||||
sb.append(HEX_DIGITS[(ch >> 12) & 15]);
|
||||
sb.append(HEX_DIGITS[(ch >> 8) & 15]);
|
||||
sb.append(HEX_DIGITS[(ch >> 4) & 15]);
|
||||
sb.append(HEX_DIGITS[(ch) & 15]);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the string to the Unicode format '\u0020'.</p>
|
||||
*
|
||||
* <p>This format is the Java source code format.</p>
|
||||
*
|
||||
* <p>If {@code null} is passed in, {@code null} will be returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.unicodeEscaped(null) = null
|
||||
* CharUtils.unicodeEscaped(' ') = "\u0020"
|
||||
* CharUtils.unicodeEscaped('A') = "\u0041"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert, may be null
|
||||
* @return the escaped Unicode string, null if null input
|
||||
*/
|
||||
public static String unicodeEscaped(final Character ch) {
|
||||
if (ch == null) {
|
||||
return null;
|
||||
}
|
||||
return unicodeEscaped(ch.charValue());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAscii('a') = true
|
||||
* CharUtils.isAscii('A') = true
|
||||
* CharUtils.isAscii('3') = true
|
||||
* CharUtils.isAscii('-') = true
|
||||
* CharUtils.isAscii('\n') = true
|
||||
* CharUtils.isAscii('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if less than 128
|
||||
*/
|
||||
public static boolean isAscii(final char ch) {
|
||||
return ch < 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit printable.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiPrintable('a') = true
|
||||
* CharUtils.isAsciiPrintable('A') = true
|
||||
* CharUtils.isAsciiPrintable('3') = true
|
||||
* CharUtils.isAsciiPrintable('-') = true
|
||||
* CharUtils.isAsciiPrintable('\n') = false
|
||||
* CharUtils.isAsciiPrintable('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 32 and 126 inclusive
|
||||
*/
|
||||
public static boolean isAsciiPrintable(final char ch) {
|
||||
return ch >= 32 && ch < 127;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit control.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiControl('a') = false
|
||||
* CharUtils.isAsciiControl('A') = false
|
||||
* CharUtils.isAsciiControl('3') = false
|
||||
* CharUtils.isAsciiControl('-') = false
|
||||
* CharUtils.isAsciiControl('\n') = true
|
||||
* CharUtils.isAsciiControl('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if less than 32 or equals 127
|
||||
*/
|
||||
public static boolean isAsciiControl(final char ch) {
|
||||
return ch < 32 || ch == 127;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlpha('a') = true
|
||||
* CharUtils.isAsciiAlpha('A') = true
|
||||
* CharUtils.isAsciiAlpha('3') = false
|
||||
* CharUtils.isAsciiAlpha('-') = false
|
||||
* CharUtils.isAsciiAlpha('\n') = false
|
||||
* CharUtils.isAsciiAlpha('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 65 and 90 or 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlpha(final char ch) {
|
||||
return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphaUpper('a') = false
|
||||
* CharUtils.isAsciiAlphaUpper('A') = true
|
||||
* CharUtils.isAsciiAlphaUpper('3') = false
|
||||
* CharUtils.isAsciiAlphaUpper('-') = false
|
||||
* CharUtils.isAsciiAlphaUpper('\n') = false
|
||||
* CharUtils.isAsciiAlphaUpper('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 65 and 90 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphaUpper(final char ch) {
|
||||
return ch >= 'A' && ch <= 'Z';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic lower case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphaLower('a') = true
|
||||
* CharUtils.isAsciiAlphaLower('A') = false
|
||||
* CharUtils.isAsciiAlphaLower('3') = false
|
||||
* CharUtils.isAsciiAlphaLower('-') = false
|
||||
* CharUtils.isAsciiAlphaLower('\n') = false
|
||||
* CharUtils.isAsciiAlphaLower('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphaLower(final char ch) {
|
||||
return ch >= 'a' && ch <= 'z';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit numeric.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiNumeric('a') = false
|
||||
* CharUtils.isAsciiNumeric('A') = false
|
||||
* CharUtils.isAsciiNumeric('3') = true
|
||||
* CharUtils.isAsciiNumeric('-') = false
|
||||
* CharUtils.isAsciiNumeric('\n') = false
|
||||
* CharUtils.isAsciiNumeric('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 48 and 57 inclusive
|
||||
*/
|
||||
public static boolean isAsciiNumeric(final char ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit numeric.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphanumeric('a') = true
|
||||
* CharUtils.isAsciiAlphanumeric('A') = true
|
||||
* CharUtils.isAsciiAlphanumeric('3') = true
|
||||
* CharUtils.isAsciiAlphanumeric('-') = false
|
||||
* CharUtils.isAsciiAlphanumeric('\n') = false
|
||||
* CharUtils.isAsciiAlphanumeric('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphanumeric(final char ch) {
|
||||
return isAsciiAlpha(ch) || isAsciiNumeric(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Compares two {@code char} values numerically. This is the same functionality as provided in Java 7.</p>
|
||||
*
|
||||
* @param x the first {@code char} to compare
|
||||
* @param y the second {@code char} to compare
|
||||
* @return the value {@code 0} if {@code x == y};
|
||||
* a value less than {@code 0} if {@code x < y}; and
|
||||
* a value greater than {@code 0} if {@code x > y}
|
||||
* @since 3.4
|
||||
*/
|
||||
public static int compare(final char x, final char y) {
|
||||
return x-y;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,230 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.math.NumberUtils;
|
||||
|
||||
/**
|
||||
* <p>An enum representing all the versions of the Java specification.
|
||||
* This is intended to mirror available values from the
|
||||
* <em>java.specification.version</em> System property. </p>
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public enum JavaVersion {
|
||||
|
||||
/**
|
||||
* The Java version reported by Android. This is not an official Java version number.
|
||||
*/
|
||||
JAVA_0_9(1.5f, "0.9"),
|
||||
|
||||
/**
|
||||
* Java 1.1.
|
||||
*/
|
||||
JAVA_1_1(1.1f, "1.1"),
|
||||
|
||||
/**
|
||||
* Java 1.2.
|
||||
*/
|
||||
JAVA_1_2(1.2f, "1.2"),
|
||||
|
||||
/**
|
||||
* Java 1.3.
|
||||
*/
|
||||
JAVA_1_3(1.3f, "1.3"),
|
||||
|
||||
/**
|
||||
* Java 1.4.
|
||||
*/
|
||||
JAVA_1_4(1.4f, "1.4"),
|
||||
|
||||
/**
|
||||
* Java 1.5.
|
||||
*/
|
||||
JAVA_1_5(1.5f, "1.5"),
|
||||
|
||||
/**
|
||||
* Java 1.6.
|
||||
*/
|
||||
JAVA_1_6(1.6f, "1.6"),
|
||||
|
||||
/**
|
||||
* Java 1.7.
|
||||
*/
|
||||
JAVA_1_7(1.7f, "1.7"),
|
||||
|
||||
/**
|
||||
* Java 1.8.
|
||||
*/
|
||||
JAVA_1_8(1.8f, "1.8"),
|
||||
|
||||
/**
|
||||
* Java 1.9.
|
||||
*
|
||||
* @deprecated As of release 3.5, replaced by {@link #JAVA_9}
|
||||
*/
|
||||
@Deprecated
|
||||
JAVA_1_9(9.0f, "9"),
|
||||
|
||||
/**
|
||||
* Java 9
|
||||
*/
|
||||
JAVA_9(9.0f, "9"),
|
||||
|
||||
/**
|
||||
* The most recent java version. Mainly introduced to avoid to break when a new version of Java is used.
|
||||
*/
|
||||
JAVA_RECENT(maxVersion(), Float.toString(maxVersion()));
|
||||
|
||||
/**
|
||||
* The float value.
|
||||
*/
|
||||
private final float value;
|
||||
/**
|
||||
* The standard name.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param value the float value
|
||||
* @param name the standard name, not null
|
||||
*/
|
||||
JavaVersion(final float value, final String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Whether this version of Java is at least the version of Java passed in.</p>
|
||||
*
|
||||
* <p>For example:<br>
|
||||
* {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}<p>
|
||||
*
|
||||
* @param requiredVersion the version to check against, not null
|
||||
* @return true if this version is equal to or greater than the specified version
|
||||
*/
|
||||
public boolean atLeast(final JavaVersion requiredVersion) {
|
||||
return this.value >= requiredVersion.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given string with a Java version number to the
|
||||
* corresponding constant of this enumeration class. This method is used
|
||||
* internally.
|
||||
*
|
||||
* @param nom the Java version as string
|
||||
* @return the corresponding enumeration constant or <b>null</b> if the
|
||||
* version is unknown
|
||||
*/
|
||||
// helper for static importing
|
||||
static JavaVersion getJavaVersion(final String nom) {
|
||||
return get(nom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given string with a Java version number to the
|
||||
* corresponding constant of this enumeration class. This method is used
|
||||
* internally.
|
||||
*
|
||||
* @param nom the Java version as string
|
||||
* @return the corresponding enumeration constant or <b>null</b> if the
|
||||
* version is unknown
|
||||
*/
|
||||
static JavaVersion get(final String nom) {
|
||||
if ("0.9".equals(nom)) {
|
||||
return JAVA_0_9;
|
||||
} else if ("1.1".equals(nom)) {
|
||||
return JAVA_1_1;
|
||||
} else if ("1.2".equals(nom)) {
|
||||
return JAVA_1_2;
|
||||
} else if ("1.3".equals(nom)) {
|
||||
return JAVA_1_3;
|
||||
} else if ("1.4".equals(nom)) {
|
||||
return JAVA_1_4;
|
||||
} else if ("1.5".equals(nom)) {
|
||||
return JAVA_1_5;
|
||||
} else if ("1.6".equals(nom)) {
|
||||
return JAVA_1_6;
|
||||
} else if ("1.7".equals(nom)) {
|
||||
return JAVA_1_7;
|
||||
} else if ("1.8".equals(nom)) {
|
||||
return JAVA_1_8;
|
||||
} else if ("9".equals(nom)) {
|
||||
return JAVA_9;
|
||||
}
|
||||
if (nom == null) {
|
||||
return null;
|
||||
}
|
||||
final float v = toFloatVersion(nom);
|
||||
if ((v - 1.) < 1.) { // then we need to check decimals > .9
|
||||
final int firstComma = Math.max(nom.indexOf('.'), nom.indexOf(','));
|
||||
final int end = Math.max(nom.length(), nom.indexOf(',', firstComma));
|
||||
if (Float.parseFloat(nom.substring(firstComma + 1, end)) > .9f) {
|
||||
return JAVA_RECENT;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>The string value is overridden to return the standard name.</p>
|
||||
*
|
||||
* <p>For example, <code>"1.5"</code>.</p>
|
||||
*
|
||||
* @return the name, not null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Java Version from the system or 99.0 if the {@code java.specification.version} system property is not set.
|
||||
*
|
||||
* @return the value of {@code java.specification.version} system property or 99.0 if it is not set.
|
||||
*/
|
||||
private static float maxVersion() {
|
||||
final float v = toFloatVersion(System.getProperty("java.specification.version", "99.0"));
|
||||
if (v > 0) {
|
||||
return v;
|
||||
}
|
||||
return 99f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a float value from a String.
|
||||
*
|
||||
* @param value the String to parse.
|
||||
* @return the float value represented by the string or -1 if the given String can not be parsed.
|
||||
*/
|
||||
private static float toFloatVersion(final String value) {
|
||||
final int defaultReturnValue = -1;
|
||||
if (value.contains(".")) {
|
||||
final String[] toParse = value.split("\\.");
|
||||
if (toParse.length >= 2) {
|
||||
return NumberUtils.toFloat(toParse[0] + '.' + toParse[1], defaultReturnValue);
|
||||
}
|
||||
} else {
|
||||
return NumberUtils.toFloat(value, defaultReturnValue);
|
||||
}
|
||||
return defaultReturnValue;
|
||||
}
|
||||
}
|
@ -1,961 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.exception.CloneFailedException;
|
||||
import com.qihoo360.replugin.ext.lang3.mutable.MutableInt;
|
||||
|
||||
/**
|
||||
* <p>Operations on {@code Object}.</p>
|
||||
*
|
||||
* <p>This class tries to handle {@code null} input gracefully.
|
||||
* An exception will generally not be thrown for a {@code null} input.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* <p>#ThreadSafe#</p>
|
||||
* @since 1.0
|
||||
*/
|
||||
//@Immutable
|
||||
@SuppressWarnings("deprecation") // deprecated class StrBuilder is imported
|
||||
// because it is part of the signature of deprecated methods
|
||||
public class ObjectUtils {
|
||||
|
||||
/**
|
||||
* <p>Singleton used as a {@code null} placeholder where
|
||||
* {@code null} has another meaning.</p>
|
||||
*
|
||||
* <p>For example, in a {@code HashMap} the
|
||||
* {@link HashMap#get(Object)} method returns
|
||||
* {@code null} if the {@code Map} contains {@code null} or if there
|
||||
* is no matching key. The {@code Null} placeholder can be used to
|
||||
* distinguish between these two cases.</p>
|
||||
*
|
||||
* <p>Another example is {@code Hashtable}, where {@code null}
|
||||
* cannot be stored.</p>
|
||||
*
|
||||
* <p>This instance is Serializable.</p>
|
||||
*/
|
||||
public static final Null NULL = new Null();
|
||||
|
||||
/**
|
||||
* <p>{@code ObjectUtils} instances should NOT be constructed in
|
||||
* standard programming. Instead, the static methods on the class should
|
||||
* be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public ObjectUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
// Defaulting
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns a default value if the object passed is {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.defaultIfNull(null, null) = null
|
||||
* ObjectUtils.defaultIfNull(null, "") = ""
|
||||
* ObjectUtils.defaultIfNull(null, "zz") = "zz"
|
||||
* ObjectUtils.defaultIfNull("abc", *) = "abc"
|
||||
* ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
* @param object the {@code Object} to test, may be {@code null}
|
||||
* @param defaultValue the default value to return, may be {@code null}
|
||||
* @return {@code object} if it is not {@code null}, defaultValue otherwise
|
||||
*/
|
||||
public static <T> T defaultIfNull(final T object, final T defaultValue) {
|
||||
return object != null ? object : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the first value in the array which is not {@code null}.
|
||||
* If all the values are {@code null} or the array is {@code null}
|
||||
* or empty then {@code null} is returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.firstNonNull(null, null) = null
|
||||
* ObjectUtils.firstNonNull(null, "") = ""
|
||||
* ObjectUtils.firstNonNull(null, null, "") = ""
|
||||
* ObjectUtils.firstNonNull(null, "zz") = "zz"
|
||||
* ObjectUtils.firstNonNull("abc", *) = "abc"
|
||||
* ObjectUtils.firstNonNull(null, "xyz", *) = "xyz"
|
||||
* ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
|
||||
* ObjectUtils.firstNonNull() = null
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the component type of the array
|
||||
* @param values the values to test, may be {@code null} or empty
|
||||
* @return the first value from {@code values} which is not {@code null},
|
||||
* or {@code null} if there are no non-null values
|
||||
* @since 3.0
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T firstNonNull(final T... values) {
|
||||
if (values != null) {
|
||||
for (final T val : values) {
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any value in the given array is not {@code null}.
|
||||
*
|
||||
* <p>
|
||||
* If all the values are {@code null} or the array is {@code null}
|
||||
* or empty then {@code false} is returned. Otherwise {@code true} is returned.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.anyNotNull(*) = true
|
||||
* ObjectUtils.anyNotNull(*, null) = true
|
||||
* ObjectUtils.anyNotNull(null, *) = true
|
||||
* ObjectUtils.anyNotNull(null, null, *, *) = true
|
||||
* ObjectUtils.anyNotNull(null) = false
|
||||
* ObjectUtils.anyNotNull(null, null) = false
|
||||
* </pre>
|
||||
*
|
||||
* @param values the values to test, may be {@code null} or empty
|
||||
* @return {@code true} if there is at least one non-null value in the array,
|
||||
* {@code false} if all values in the array are {@code null}s.
|
||||
* If the array is {@code null} or empty {@code false} is also returned.
|
||||
* @since 3.5
|
||||
*/
|
||||
public static boolean anyNotNull(final Object... values) {
|
||||
return firstNonNull(values) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all values in the array are not {@code nulls}.
|
||||
*
|
||||
* <p>
|
||||
* If any value is {@code null} or the array is {@code null} then
|
||||
* {@code false} is returned. If all elements in array are not
|
||||
* {@code null} or the array is empty (contains no elements) {@code true}
|
||||
* is returned.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.allNotNull(*) = true
|
||||
* ObjectUtils.allNotNull(*, *) = true
|
||||
* ObjectUtils.allNotNull(null) = false
|
||||
* ObjectUtils.allNotNull(null, null) = false
|
||||
* ObjectUtils.allNotNull(null, *) = false
|
||||
* ObjectUtils.allNotNull(*, null) = false
|
||||
* ObjectUtils.allNotNull(*, *, null, *) = false
|
||||
* </pre>
|
||||
*
|
||||
* @param values the values to test, may be {@code null} or empty
|
||||
* @return {@code false} if there is at least one {@code null} value in the array or the array is {@code null},
|
||||
* {@code true} if all values in the array are not {@code null}s or array contains no elements.
|
||||
* @since 3.5
|
||||
*/
|
||||
public static boolean allNotNull(final Object... values) {
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final Object val : values) {
|
||||
if (val == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Null-safe equals/hashCode
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compares two objects for equality, where either one or both
|
||||
* objects may be {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.equals(null, null) = true
|
||||
* ObjectUtils.equals(null, "") = false
|
||||
* ObjectUtils.equals("", null) = false
|
||||
* ObjectUtils.equals("", "") = true
|
||||
* ObjectUtils.equals(Boolean.TRUE, null) = false
|
||||
* ObjectUtils.equals(Boolean.TRUE, "true") = false
|
||||
* ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE) = true
|
||||
* ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
|
||||
* </pre>
|
||||
*
|
||||
* @param object1 the first object, may be {@code null}
|
||||
* @param object2 the second object, may be {@code null}
|
||||
* @return {@code true} if the values of both objects are the same
|
||||
* @deprecated this method has been replaced by {@code java.util.Objects.equals(Object, Object)} in Java 7 and will
|
||||
* be removed from future releases.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean equals(final Object object1, final Object object2) {
|
||||
if (object1 == object2) {
|
||||
return true;
|
||||
}
|
||||
if (object1 == null || object2 == null) {
|
||||
return false;
|
||||
}
|
||||
return object1.equals(object2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Compares two objects for inequality, where either one or both
|
||||
* objects may be {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.notEqual(null, null) = false
|
||||
* ObjectUtils.notEqual(null, "") = true
|
||||
* ObjectUtils.notEqual("", null) = true
|
||||
* ObjectUtils.notEqual("", "") = false
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, null) = true
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, "true") = true
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE) = false
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
|
||||
* </pre>
|
||||
*
|
||||
* @param object1 the first object, may be {@code null}
|
||||
* @param object2 the second object, may be {@code null}
|
||||
* @return {@code false} if the values of both objects are the same
|
||||
*/
|
||||
public static boolean notEqual(final Object object1, final Object object2) {
|
||||
return ObjectUtils.equals(object1, object2) == false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the hash code of an object returning zero when the
|
||||
* object is {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.hashCode(null) = 0
|
||||
* ObjectUtils.hashCode(obj) = obj.hashCode()
|
||||
* </pre>
|
||||
*
|
||||
* @param obj the object to obtain the hash code of, may be {@code null}
|
||||
* @return the hash code of the object, or zero if null
|
||||
* @since 2.1
|
||||
* @deprecated this method has been replaced by {@code java.util.Objects.hashCode(Object)} in Java 7 and will be
|
||||
* removed in future releases
|
||||
*/
|
||||
@Deprecated
|
||||
public static int hashCode(final Object obj) {
|
||||
// hashCode(Object) retained for performance, as hash code is often critical
|
||||
return obj == null ? 0 : obj.hashCode();
|
||||
}
|
||||
|
||||
// Identity ToString
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the toString that would be produced by {@code Object}
|
||||
* if a class did not override toString itself. {@code null}
|
||||
* will return {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.identityToString(null) = null
|
||||
* ObjectUtils.identityToString("") = "java.lang.String@1e23"
|
||||
* ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
|
||||
* </pre>
|
||||
*
|
||||
* @param object the object to create a toString for, may be
|
||||
* {@code null}
|
||||
* @return the default toString text, or {@code null} if
|
||||
* {@code null} passed in
|
||||
*/
|
||||
public static String identityToString(final Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
identityToString(builder, object);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Appends the toString that would be produced by {@code Object}
|
||||
* if a class did not override toString itself. {@code null}
|
||||
* will throw a NullPointerException for either of the two parameters. </p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.identityToString(buf, "") = buf.append("java.lang.String@1e23"
|
||||
* ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa"
|
||||
* ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa")
|
||||
* </pre>
|
||||
*
|
||||
* @param buffer the buffer to append to
|
||||
* @param object the object to create a toString for
|
||||
* @since 2.4
|
||||
*/
|
||||
public static void identityToString(final StringBuffer buffer, final Object object) {
|
||||
Validate.notNull(object, "Cannot get the toString of a null identity");
|
||||
buffer.append(object.getClass().getName())
|
||||
.append('@')
|
||||
.append(Integer.toHexString(System.identityHashCode(object)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Appends the toString that would be produced by {@code Object}
|
||||
* if a class did not override toString itself. {@code null}
|
||||
* will throw a NullPointerException for either of the two parameters. </p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.identityToString(builder, "") = builder.append("java.lang.String@1e23"
|
||||
* ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa"
|
||||
* ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa")
|
||||
* </pre>
|
||||
*
|
||||
* @param builder the builder to append to
|
||||
* @param object the object to create a toString for
|
||||
* @since 3.2
|
||||
*/
|
||||
public static void identityToString(final StringBuilder builder, final Object object) {
|
||||
Validate.notNull(object, "Cannot get the toString of a null identity");
|
||||
builder.append(object.getClass().getName())
|
||||
.append('@')
|
||||
.append(Integer.toHexString(System.identityHashCode(object)));
|
||||
}
|
||||
|
||||
// ToString
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the {@code toString} of an {@code Object} returning
|
||||
* an empty string ("") if {@code null} input.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.toString(null) = ""
|
||||
* ObjectUtils.toString("") = ""
|
||||
* ObjectUtils.toString("bat") = "bat"
|
||||
* ObjectUtils.toString(Boolean.TRUE) = "true"
|
||||
* </pre>
|
||||
*
|
||||
* @see StringUtils#defaultString(String)
|
||||
* @see String#valueOf(Object)
|
||||
* @param obj the Object to {@code toString}, may be null
|
||||
* @return the passed in Object's toString, or {@code ""} if {@code null} input
|
||||
* @since 2.0
|
||||
* @deprecated this method has been replaced by {@code java.util.Objects.toString(Object)} in Java 7 and will be
|
||||
* removed in future releases. Note however that said method will return "null" for null references, while this
|
||||
* method returns an empty String. To preserve behavior use {@code java.util.Objects.toString(myObject, "")}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String toString(final Object obj) {
|
||||
return obj == null ? StringUtils.EMPTY : obj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the {@code toString} of an {@code Object} returning
|
||||
* a specified text if {@code null} input.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.toString(null, null) = null
|
||||
* ObjectUtils.toString(null, "null") = "null"
|
||||
* ObjectUtils.toString("", "null") = ""
|
||||
* ObjectUtils.toString("bat", "null") = "bat"
|
||||
* ObjectUtils.toString(Boolean.TRUE, "null") = "true"
|
||||
* </pre>
|
||||
*
|
||||
* @see StringUtils#defaultString(String,String)
|
||||
* @see String#valueOf(Object)
|
||||
* @param obj the Object to {@code toString}, may be null
|
||||
* @param nullStr the String to return if {@code null} input, may be null
|
||||
* @return the passed in Object's toString, or {@code nullStr} if {@code null} input
|
||||
* @since 2.0
|
||||
* @deprecated this method has been replaced by {@code java.util.Objects.toString(Object, String)} in Java 7 and
|
||||
* will be removed in future releases.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String toString(final Object obj, final String nullStr) {
|
||||
return obj == null ? nullStr : obj.toString();
|
||||
}
|
||||
|
||||
// Comparable
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param values the set of comparable values, may be null
|
||||
* @return
|
||||
* <ul>
|
||||
* <li>If any objects are non-null and unequal, the lesser object.
|
||||
* <li>If all objects are non-null and equal, the first.
|
||||
* <li>If any of the comparables are null, the lesser of the non-null objects.
|
||||
* <li>If all the comparables are null, null is returned.
|
||||
* </ul>
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T extends Comparable<? super T>> T min(final T... values) {
|
||||
T result = null;
|
||||
if (values != null) {
|
||||
for (final T value : values) {
|
||||
if (compare(value, result, true) < 0) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param values the set of comparable values, may be null
|
||||
* @return
|
||||
* <ul>
|
||||
* <li>If any objects are non-null and unequal, the greater object.
|
||||
* <li>If all objects are non-null and equal, the first.
|
||||
* <li>If any of the comparables are null, the greater of the non-null objects.
|
||||
* <li>If all the comparables are null, null is returned.
|
||||
* </ul>
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T extends Comparable<? super T>> T max(final T... values) {
|
||||
T result = null;
|
||||
if (values != null) {
|
||||
for (final T value : values) {
|
||||
if (compare(value, result, false) > 0) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.
|
||||
* {@code null} is assumed to be less than a non-{@code null} value.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param c1 the first comparable, may be null
|
||||
* @param c2 the second comparable, may be null
|
||||
* @return a negative value if c1 < c2, zero if c1 = c2
|
||||
* and a positive value if c1 > c2
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> int compare(final T c1, final T c2) {
|
||||
return compare(c1, c2, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param c1 the first comparable, may be null
|
||||
* @param c2 the second comparable, may be null
|
||||
* @param nullGreater if true {@code null} is considered greater
|
||||
* than a non-{@code null} value or if false {@code null} is
|
||||
* considered less than a Non-{@code null} value
|
||||
* @return a negative value if c1 < c2, zero if c1 = c2
|
||||
* and a positive value if c1 > c2
|
||||
* @see Comparator#compare(Object, Object)
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> int compare(final T c1, final T c2, final boolean nullGreater) {
|
||||
if (c1 == c2) {
|
||||
return 0;
|
||||
} else if (c1 == null) {
|
||||
return nullGreater ? 1 : -1;
|
||||
} else if (c2 == null) {
|
||||
return nullGreater ? -1 : 1;
|
||||
}
|
||||
return c1.compareTo(c2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "best guess" middle value among comparables. If there is an even
|
||||
* number of total values, the lower of the two middle values will be returned.
|
||||
* @param <T> type of values processed by this method
|
||||
* @param items to compare
|
||||
* @return T at middle position
|
||||
* @throws NullPointerException if items is {@code null}
|
||||
* @throws IllegalArgumentException if items is empty or contains {@code null} values
|
||||
* @since 3.0.1
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T extends Comparable<? super T>> T median(final T... items) {
|
||||
Validate.notEmpty(items);
|
||||
Validate.noNullElements(items);
|
||||
final TreeSet<T> sort = new TreeSet<>();
|
||||
Collections.addAll(sort, items);
|
||||
@SuppressWarnings("unchecked") //we know all items added were T instances
|
||||
final T result = (T) sort.toArray()[(sort.size() - 1) / 2];
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "best guess" middle value among comparables. If there is an even
|
||||
* number of total values, the lower of the two middle values will be returned.
|
||||
* @param <T> type of values processed by this method
|
||||
* @param comparator to use for comparisons
|
||||
* @param items to compare
|
||||
* @return T at middle position
|
||||
* @throws NullPointerException if items or comparator is {@code null}
|
||||
* @throws IllegalArgumentException if items is empty or contains {@code null} values
|
||||
* @since 3.0.1
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T median(final Comparator<T> comparator, final T... items) {
|
||||
Validate.notEmpty(items, "null/empty items");
|
||||
Validate.noNullElements(items);
|
||||
Validate.notNull(comparator, "null comparator");
|
||||
final TreeSet<T> sort = new TreeSet<>(comparator);
|
||||
Collections.addAll(sort, items);
|
||||
@SuppressWarnings("unchecked") //we know all items added were T instances
|
||||
final
|
||||
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Mode
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Find the most frequently occurring item.
|
||||
*
|
||||
* @param <T> type of values processed by this method
|
||||
* @param items to check
|
||||
* @return most populous T, {@code null} if non-unique or no items supplied
|
||||
* @since 3.0.1
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> T mode(final T... items) {
|
||||
if (ArrayUtils.isNotEmpty(items)) {
|
||||
final HashMap<T, MutableInt> occurrences = new HashMap<>(items.length);
|
||||
for (final T t : items) {
|
||||
final MutableInt count = occurrences.get(t);
|
||||
if (count == null) {
|
||||
occurrences.put(t, new MutableInt(1));
|
||||
} else {
|
||||
count.increment();
|
||||
}
|
||||
}
|
||||
T result = null;
|
||||
int max = 0;
|
||||
for (final Map.Entry<T, MutableInt> e : occurrences.entrySet()) {
|
||||
final int cmp = e.getValue().intValue();
|
||||
if (cmp == max) {
|
||||
result = null;
|
||||
} else if (cmp > max) {
|
||||
max = cmp;
|
||||
result = e.getKey();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// cloning
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Clone an object.</p>
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
* @param obj the object to clone, null returns null
|
||||
* @return the clone if the object implements {@link Cloneable} otherwise {@code null}
|
||||
* @throws CloneFailedException if the object is cloneable and the clone operation fails
|
||||
* @since 3.0
|
||||
*/
|
||||
public static <T> T clone(final T obj) {
|
||||
if (obj instanceof Cloneable) {
|
||||
final Object result;
|
||||
if (obj.getClass().isArray()) {
|
||||
final Class<?> componentType = obj.getClass().getComponentType();
|
||||
if (!componentType.isPrimitive()) {
|
||||
result = ((Object[]) obj).clone();
|
||||
} else {
|
||||
int length = Array.getLength(obj);
|
||||
result = Array.newInstance(componentType, length);
|
||||
while (length-- > 0) {
|
||||
Array.set(result, length, Array.get(obj, length));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
final Method clone = obj.getClass().getMethod("clone");
|
||||
result = clone.invoke(obj);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw new CloneFailedException("Cloneable type "
|
||||
+ obj.getClass().getName()
|
||||
+ " has no clone method", e);
|
||||
} catch (final IllegalAccessException e) {
|
||||
throw new CloneFailedException("Cannot clone Cloneable type "
|
||||
+ obj.getClass().getName(), e);
|
||||
} catch (final InvocationTargetException e) {
|
||||
throw new CloneFailedException("Exception cloning Cloneable type "
|
||||
+ obj.getClass().getName(), e.getCause());
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked") // OK because input is of type T
|
||||
final T checked = (T) result;
|
||||
return checked;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clone an object if possible.</p>
|
||||
*
|
||||
* <p>This method is similar to {@link #clone(Object)}, but will return the provided
|
||||
* instance as the return value instead of {@code null} if the instance
|
||||
* is not cloneable. This is more convenient if the caller uses different
|
||||
* implementations (e.g. of a service) and some of the implementations do not allow concurrent
|
||||
* processing or have state. In such cases the implementation can simply provide a proper
|
||||
* clone implementation and the caller's code does not have to change.</p>
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
* @param obj the object to clone, null returns null
|
||||
* @return the clone if the object implements {@link Cloneable} otherwise the object itself
|
||||
* @throws CloneFailedException if the object is cloneable and the clone operation fails
|
||||
* @since 3.0
|
||||
*/
|
||||
public static <T> T cloneIfPossible(final T obj) {
|
||||
final T clone = clone(obj);
|
||||
return clone == null ? obj : clone;
|
||||
}
|
||||
|
||||
// Null
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Class used as a null placeholder where {@code null}
|
||||
* has another meaning.</p>
|
||||
*
|
||||
* <p>For example, in a {@code HashMap} the
|
||||
* {@link HashMap#get(Object)} method returns
|
||||
* {@code null} if the {@code Map} contains {@code null} or if there is
|
||||
* no matching key. The {@code Null} placeholder can be used to distinguish
|
||||
* between these two cases.</p>
|
||||
*
|
||||
* <p>Another example is {@code Hashtable}, where {@code null}
|
||||
* cannot be stored.</p>
|
||||
*/
|
||||
public static class Null implements Serializable {
|
||||
/**
|
||||
* Required for serialization support. Declare serialization compatibility with Commons Lang 1.0
|
||||
*
|
||||
* @see Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 7092611880189329093L;
|
||||
|
||||
/**
|
||||
* Restricted constructor - singleton.
|
||||
*/
|
||||
Null() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Ensure singleton.</p>
|
||||
*
|
||||
* @return the singleton value
|
||||
*/
|
||||
private Object readResolve() {
|
||||
return ObjectUtils.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Constants (LANG-816):
|
||||
/*
|
||||
These methods ensure constants are not inlined by javac.
|
||||
For example, typically a developer might declare a constant like so:
|
||||
|
||||
public final static int MAGIC_NUMBER = 5;
|
||||
|
||||
Should a different jar file refer to this, and the MAGIC_NUMBER
|
||||
is changed a later date (e.g., MAGIC_NUMBER = 6), the different jar
|
||||
file will need to recompile itself. This is because javac
|
||||
typically inlines the primitive or String constant directly into
|
||||
the bytecode, and removes the reference to the MAGIC_NUMBER field.
|
||||
|
||||
To help the other jar (so that it does not need to recompile
|
||||
when constants are changed) the original developer can declare
|
||||
their constant using one of the CONST() utility methods, instead:
|
||||
|
||||
public final static int MAGIC_NUMBER = CONST(5);
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static boolean MAGIC_FLAG = ObjectUtils.CONST(true);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the boolean value to return
|
||||
* @return the boolean v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static boolean CONST(final boolean v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static byte MAGIC_BYTE = ObjectUtils.CONST((byte) 127);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the byte value to return
|
||||
* @return the byte v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static byte CONST(final byte v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static byte MAGIC_BYTE = ObjectUtils.CONST_BYTE(127);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the byte literal (as an int) value to return
|
||||
* @throws IllegalArgumentException if the value passed to v
|
||||
* is larger than a byte, that is, smaller than -128 or
|
||||
* larger than 127.
|
||||
* @return the byte v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static byte CONST_BYTE(final int v) throws IllegalArgumentException {
|
||||
if (v < Byte.MIN_VALUE || v > Byte.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Supplied value must be a valid byte literal between -128 and 127: [" + v + "]");
|
||||
}
|
||||
return (byte) v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static char MAGIC_CHAR = ObjectUtils.CONST('a');
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the char value to return
|
||||
* @return the char v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static char CONST(final char v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static short MAGIC_SHORT = ObjectUtils.CONST((short) 123);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the short value to return
|
||||
* @return the short v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static short CONST(final short v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static short MAGIC_SHORT = ObjectUtils.CONST_SHORT(127);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the short literal (as an int) value to return
|
||||
* @throws IllegalArgumentException if the value passed to v
|
||||
* is larger than a short, that is, smaller than -32768 or
|
||||
* larger than 32767.
|
||||
* @return the byte v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static short CONST_SHORT(final int v) throws IllegalArgumentException {
|
||||
if (v < Short.MIN_VALUE || v > Short.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Supplied value must be a valid byte literal between -32768 and 32767: [" + v + "]");
|
||||
}
|
||||
return (short) v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static int MAGIC_INT = ObjectUtils.CONST(123);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the int value to return
|
||||
* @return the int v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static int CONST(final int v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static long MAGIC_LONG = ObjectUtils.CONST(123L);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the long value to return
|
||||
* @return the long v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static long CONST(final long v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static float MAGIC_FLOAT = ObjectUtils.CONST(1.0f);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the float value to return
|
||||
* @return the float v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static float CONST(final float v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static double MAGIC_DOUBLE = ObjectUtils.CONST(1.0);
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param v the double value to return
|
||||
* @return the double v, unchanged
|
||||
* @since 3.2
|
||||
*/
|
||||
public static double CONST(final double v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the provided value unchanged.
|
||||
* This can prevent javac from inlining a constant
|
||||
* field, e.g.,
|
||||
*
|
||||
* <pre>
|
||||
* public final static String MAGIC_STRING = ObjectUtils.CONST("abc");
|
||||
* </pre>
|
||||
*
|
||||
* This way any jars that refer to this field do not
|
||||
* have to recompile themselves if the field's value
|
||||
* changes at some future date.
|
||||
*
|
||||
* @param <T> the Object type
|
||||
* @param v the genericized Object value to return (typically a String).
|
||||
* @return the genericized Object v, unchanged (typically a String).
|
||||
* @since 3.2
|
||||
*/
|
||||
public static <T> T CONST(final T v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The Builder interface is designed to designate a class as a <em>builder</em>
|
||||
* object in the Builder design pattern. Builders are capable of creating and
|
||||
* configuring objects or results that normally take multiple steps to construct
|
||||
* or are very complex to derive.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The builder interface defines a single method, {@link #build()}, that
|
||||
* classes must implement. The result of this method should be the final
|
||||
* configured object or result after all building operations are performed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It is a recommended practice that the methods supplied to configure the
|
||||
* object or result being built return a reference to {@code this} so that
|
||||
* method calls can be chained together.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Example Builder:
|
||||
* <pre><code>
|
||||
* class FontBuilder implements Builder<Font> {
|
||||
* private Font font;
|
||||
*
|
||||
* public FontBuilder(String fontName) {
|
||||
* this.font = new Font(fontName, Font.PLAIN, 12);
|
||||
* }
|
||||
*
|
||||
* public FontBuilder bold() {
|
||||
* this.font = this.font.deriveFont(Font.BOLD);
|
||||
* return this; // Reference returned so calls can be chained
|
||||
* }
|
||||
*
|
||||
* public FontBuilder size(float pointSize) {
|
||||
* this.font = this.font.deriveFont(pointSize);
|
||||
* return this; // Reference returned so calls can be chained
|
||||
* }
|
||||
*
|
||||
* // Other Font construction methods
|
||||
*
|
||||
* public Font build() {
|
||||
* return this.font;
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* Example Builder Usage:
|
||||
* <pre><code>
|
||||
* Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
|
||||
* .size(14.0f)
|
||||
* .build();
|
||||
* </code></pre>
|
||||
*
|
||||
*
|
||||
* @param <T> the type of object that the builder will construct or compute.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface Builder<T> {
|
||||
|
||||
/**
|
||||
* Returns a reference to the object being constructed or result being
|
||||
* calculated by the builder.
|
||||
*
|
||||
* @return the object constructed or result calculated by the builder.
|
||||
*/
|
||||
T build();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,118 +0,0 @@
|
||||
/**
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ObjectUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.TypeUtils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@code Diff} contains the differences between two {@link Diffable} class
|
||||
* fields.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Typically, {@code Diff}s are retrieved by using a {@link DiffBuilder} to
|
||||
* produce a {@link DiffResult}, containing the differences between two objects.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param <T>
|
||||
* The type of object contained within this {@code Diff}. Differences
|
||||
* between primitive objects are stored as their Object wrapper
|
||||
* equivalent.
|
||||
* @since 3.3
|
||||
*/
|
||||
public abstract class Diff<T> extends Pair<T, T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Type type;
|
||||
private final String fieldName;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructs a new {@code Diff} for the given field name.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the name of the field
|
||||
*/
|
||||
protected Diff(final String fieldName) {
|
||||
this.type = ObjectUtils.defaultIfNull(
|
||||
TypeUtils.getTypeArguments(getClass(), Diff.class).get(
|
||||
Diff.class.getTypeParameters()[0]), Object.class);
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the type of the field.
|
||||
* </p>
|
||||
*
|
||||
* @return the field type
|
||||
*/
|
||||
public final Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the name of the field.
|
||||
* </p>
|
||||
*
|
||||
* @return the field name
|
||||
*/
|
||||
public final String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns a {@code String} representation of the {@code Diff}, with the
|
||||
* following format:</p>
|
||||
*
|
||||
* <pre>
|
||||
* [fieldname: left-value, right-value]
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @return the string representation
|
||||
*/
|
||||
@Override
|
||||
public final String toString() {
|
||||
return String.format("[%s: %s, %s]", fieldName, getLeft(), getRight());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Throws {@code UnsupportedOperationException}.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* ignored
|
||||
* @return nothing
|
||||
*/
|
||||
@Override
|
||||
public final T setValue(final T value) {
|
||||
throw new UnsupportedOperationException("Cannot alter Diff object.");
|
||||
}
|
||||
}
|
@ -1,984 +0,0 @@
|
||||
/**
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ArrayUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Assists in implementing {@link Diffable#diff(Object)} methods.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To use this class, write code as follows:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class Person implements Diffable<Person> {
|
||||
* String name;
|
||||
* int age;
|
||||
* boolean smoker;
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* public DiffResult diff(Person obj) {
|
||||
* // No need for null check, as NullPointerException correct if obj is null
|
||||
* return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
* .append("name", this.name, obj.name)
|
||||
* .append("age", this.age, obj.age)
|
||||
* .append("smoker", this.smoker, obj.smoker)
|
||||
* .build();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The {@code ToStringStyle} passed to the constructor is embedded in the
|
||||
* returned {@code DiffResult} and influences the style of the
|
||||
* {@code DiffResult.toString()} method. This style choice can be overridden by
|
||||
* calling {@link DiffResult#toString(ToStringStyle)}.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.3
|
||||
* @see Diffable
|
||||
* @see Diff
|
||||
* @see DiffResult
|
||||
* @see ToStringStyle
|
||||
*/
|
||||
public class DiffBuilder implements Builder<DiffResult> {
|
||||
|
||||
private final List<Diff<?>> diffs;
|
||||
private final boolean objectsTriviallyEqual;
|
||||
private final Object left;
|
||||
private final Object right;
|
||||
private final ToStringStyle style;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructs a builder for the specified objects with the specified style.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will
|
||||
* not evaluate any calls to {@code append(...)} and will return an empty
|
||||
* {@link DiffResult} when {@link #build()} is executed.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs
|
||||
* {@code this} object
|
||||
* @param rhs
|
||||
* the object to diff against
|
||||
* @param style
|
||||
* the style will use when outputting the objects, {@code null}
|
||||
* uses the default
|
||||
* @param testTriviallyEqual
|
||||
* If true, this will test if lhs and rhs are the same or equal.
|
||||
* All of the append(fieldName, lhs, rhs) methods will abort
|
||||
* without creating a field {@link Diff} if the trivially equal
|
||||
* test is enabled and returns true. The result of this test
|
||||
* is never changed throughout the life of this {@link DiffBuilder}.
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code lhs} or {@code rhs} is {@code null}
|
||||
* @since 3.4
|
||||
*/
|
||||
public DiffBuilder(final Object lhs, final Object rhs,
|
||||
final ToStringStyle style, final boolean testTriviallyEqual) {
|
||||
|
||||
Validate.isTrue(lhs != null, "lhs cannot be null");
|
||||
Validate.isTrue(rhs != null, "rhs cannot be null");
|
||||
|
||||
this.diffs = new ArrayList<>();
|
||||
this.left = lhs;
|
||||
this.right = rhs;
|
||||
this.style = style;
|
||||
|
||||
// Don't compare any fields if objects equal
|
||||
this.objectsTriviallyEqual = testTriviallyEqual && (lhs == rhs || lhs.equals(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructs a builder for the specified objects with the specified style.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will
|
||||
* not evaluate any calls to {@code append(...)} and will return an empty
|
||||
* {@link DiffResult} when {@link #build()} is executed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)}
|
||||
* with the testTriviallyEqual flag enabled.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs
|
||||
* {@code this} object
|
||||
* @param rhs
|
||||
* the object to diff against
|
||||
* @param style
|
||||
* the style will use when outputting the objects, {@code null}
|
||||
* uses the default
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code lhs} or {@code rhs} is {@code null}
|
||||
*/
|
||||
public DiffBuilder(final Object lhs, final Object rhs,
|
||||
final ToStringStyle style) {
|
||||
|
||||
this(lhs, rhs, style, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code boolean}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code boolean}
|
||||
* @param rhs
|
||||
* the right hand {@code boolean}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final boolean lhs,
|
||||
final boolean rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
diffs.add(new Diff<Boolean>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Boolean getLeft() {
|
||||
return Boolean.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getRight() {
|
||||
return Boolean.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code boolean[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code boolean[]}
|
||||
* @param rhs
|
||||
* the right hand {@code boolean[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final boolean[] lhs,
|
||||
final boolean[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Boolean[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Boolean[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code byte}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code byte}
|
||||
* @param rhs
|
||||
* the right hand {@code byte}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final byte lhs,
|
||||
final byte rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
diffs.add(new Diff<Byte>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Byte getLeft() {
|
||||
return Byte.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte getRight() {
|
||||
return Byte.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code byte[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code byte[]}
|
||||
* @param rhs
|
||||
* the right hand {@code byte[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final byte[] lhs,
|
||||
final byte[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Byte[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Byte[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code char}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code char}
|
||||
* @param rhs
|
||||
* the right hand {@code char}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final char lhs,
|
||||
final char rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
diffs.add(new Diff<Character>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Character getLeft() {
|
||||
return Character.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character getRight() {
|
||||
return Character.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code char[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code char[]}
|
||||
* @param rhs
|
||||
* the right hand {@code char[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final char[] lhs,
|
||||
final char[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Character[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Character[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code double}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code double}
|
||||
* @param rhs
|
||||
* the right hand {@code double}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final double lhs,
|
||||
final double rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (Double.doubleToLongBits(lhs) != Double.doubleToLongBits(rhs)) {
|
||||
diffs.add(new Diff<Double>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Double getLeft() {
|
||||
return Double.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getRight() {
|
||||
return Double.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code double[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code double[]}
|
||||
* @param rhs
|
||||
* the right hand {@code double[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final double[] lhs,
|
||||
final double[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Double[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Double[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code float}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code float}
|
||||
* @param rhs
|
||||
* the right hand {@code float}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final float lhs,
|
||||
final float rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (Float.floatToIntBits(lhs) != Float.floatToIntBits(rhs)) {
|
||||
diffs.add(new Diff<Float>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Float getLeft() {
|
||||
return Float.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float getRight() {
|
||||
return Float.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code float[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code float[]}
|
||||
* @param rhs
|
||||
* the right hand {@code float[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final float[] lhs,
|
||||
final float[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Float[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Float[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code int}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code int}
|
||||
* @param rhs
|
||||
* the right hand {@code int}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final int lhs,
|
||||
final int rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
diffs.add(new Diff<Integer>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getLeft() {
|
||||
return Integer.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getRight() {
|
||||
return Integer.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code int[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code int[]}
|
||||
* @param rhs
|
||||
* the right hand {@code int[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final int[] lhs,
|
||||
final int[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Integer[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code long}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code long}
|
||||
* @param rhs
|
||||
* the right hand {@code long}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final long lhs,
|
||||
final long rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
diffs.add(new Diff<Long>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Long getLeft() {
|
||||
return Long.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getRight() {
|
||||
return Long.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code long[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code long[]}
|
||||
* @param rhs
|
||||
* the right hand {@code long[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final long[] lhs,
|
||||
final long[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Long[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Long[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code short}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code short}
|
||||
* @param rhs
|
||||
* the right hand {@code short}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final short lhs,
|
||||
final short rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs != rhs) {
|
||||
diffs.add(new Diff<Short>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Short getLeft() {
|
||||
return Short.valueOf(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short getRight() {
|
||||
return Short.valueOf(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code short[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code short[]}
|
||||
* @param rhs
|
||||
* the right hand {@code short[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final short[] lhs,
|
||||
final short[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Short[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Short[] getLeft() {
|
||||
return ArrayUtils.toObject(lhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short[] getRight() {
|
||||
return ArrayUtils.toObject(rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code Objects}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code Object}
|
||||
* @param rhs
|
||||
* the right hand {@code Object}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final Object lhs,
|
||||
final Object rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Object objectToTest;
|
||||
if (lhs != null) {
|
||||
objectToTest = lhs;
|
||||
} else {
|
||||
// rhs cannot be null, as lhs != rhs
|
||||
objectToTest = rhs;
|
||||
}
|
||||
|
||||
if (objectToTest.getClass().isArray()) {
|
||||
if (objectToTest instanceof boolean[]) {
|
||||
return append(fieldName, (boolean[]) lhs, (boolean[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof byte[]) {
|
||||
return append(fieldName, (byte[]) lhs, (byte[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof char[]) {
|
||||
return append(fieldName, (char[]) lhs, (char[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof double[]) {
|
||||
return append(fieldName, (double[]) lhs, (double[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof float[]) {
|
||||
return append(fieldName, (float[]) lhs, (float[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof int[]) {
|
||||
return append(fieldName, (int[]) lhs, (int[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof long[]) {
|
||||
return append(fieldName, (long[]) lhs, (long[]) rhs);
|
||||
}
|
||||
if (objectToTest instanceof short[]) {
|
||||
return append(fieldName, (short[]) lhs, (short[]) rhs);
|
||||
}
|
||||
|
||||
return append(fieldName, (Object[]) lhs, (Object[]) rhs);
|
||||
}
|
||||
|
||||
// Not array type
|
||||
if (lhs != null && lhs.equals(rhs)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
diffs.add(new Diff<Object>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Object getLeft() {
|
||||
return lhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getRight() {
|
||||
return rhs;
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two {@code Object[]}s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param lhs
|
||||
* the left hand {@code Object[]}
|
||||
* @param rhs
|
||||
* the right hand {@code Object[]}
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName, final Object[] lhs,
|
||||
final Object[] rhs) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(lhs, rhs)) {
|
||||
diffs.add(new Diff<Object[]>(fieldName) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Object[] getLeft() {
|
||||
return lhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getRight() {
|
||||
return rhs;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append diffs from another {@code DiffResult}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This method is useful if you want to compare properties which are
|
||||
* themselves Diffable and would like to know which specific part of
|
||||
* it is different.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class Person implements Diffable<Person> {
|
||||
* String name;
|
||||
* Address address; // implements Diffable<Address>
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* public DiffResult diff(Person obj) {
|
||||
* return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
* .append("name", this.name, obj.name)
|
||||
* .append("address", this.address.diff(obj.address))
|
||||
* .build();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param fieldName
|
||||
* the field name
|
||||
* @param diffResult
|
||||
* the {@code DiffResult} to append
|
||||
* @return this
|
||||
* @throws IllegalArgumentException
|
||||
* if field name is {@code null}
|
||||
* @since 3.5
|
||||
*/
|
||||
public DiffBuilder append(final String fieldName,
|
||||
final DiffResult diffResult) {
|
||||
validateFieldNameNotNull(fieldName);
|
||||
Validate.isTrue(diffResult != null, "Diff result cannot be null");
|
||||
if (objectsTriviallyEqual) {
|
||||
return this;
|
||||
}
|
||||
|
||||
for (final Diff<?> diff : diffResult.getDiffs()) {
|
||||
append(fieldName + "." + diff.getFieldName(),
|
||||
diff.getLeft(), diff.getRight());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a {@link DiffResult} based on the differences appended to this
|
||||
* builder.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@code DiffResult} containing the differences between the two
|
||||
* objects.
|
||||
*/
|
||||
@Override
|
||||
public DiffResult build() {
|
||||
return new DiffResult(left, right, diffs, style);
|
||||
}
|
||||
|
||||
private void validateFieldNameNotNull(final String fieldName) {
|
||||
Validate.isTrue(fieldName != null, "Field name cannot be null");
|
||||
}
|
||||
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
/**
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@code DiffResult} contains a collection of the differences between two
|
||||
* {@link Diffable} objects. Typically these differences are displayed using
|
||||
* {@link #toString()} method, which returns a string describing the fields that
|
||||
* differ between the objects.
|
||||
* </p>
|
||||
* <p>
|
||||
* Use a {@link DiffBuilder} to build a {@code DiffResult} comparing two objects.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public class DiffResult implements Iterable<Diff<?>> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The {@code String} returned when the objects have no differences:
|
||||
* {@value}
|
||||
* </p>
|
||||
*/
|
||||
public static final String OBJECTS_SAME_STRING = "";
|
||||
|
||||
private static final String DIFFERS_STRING = "differs from";
|
||||
|
||||
private final List<Diff<?>> diffs;
|
||||
private final Object lhs;
|
||||
private final Object rhs;
|
||||
private final ToStringStyle style;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a {@link DiffResult} containing the differences between two
|
||||
* objects.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs
|
||||
* the left hand object
|
||||
* @param rhs
|
||||
* the right hand object
|
||||
* @param diffs
|
||||
* the list of differences, may be empty
|
||||
* @param style
|
||||
* the style to use for the {@link #toString()} method. May be
|
||||
* {@code null}, in which case
|
||||
* {@link ToStringStyle#DEFAULT_STYLE} is used
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code lhs}, {@code rhs} or {@code diffs} is {@code null}
|
||||
*/
|
||||
DiffResult(final Object lhs, final Object rhs, final List<Diff<?>> diffs,
|
||||
final ToStringStyle style) {
|
||||
Validate.isTrue(lhs != null, "Left hand object cannot be null");
|
||||
Validate.isTrue(rhs != null, "Right hand object cannot be null");
|
||||
Validate.isTrue(diffs != null, "List of differences cannot be null");
|
||||
|
||||
this.diffs = diffs;
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
|
||||
if (style == null) {
|
||||
this.style = ToStringStyle.DEFAULT_STYLE;
|
||||
} else {
|
||||
this.style = style;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns an unmodifiable list of {@code Diff}s. The list may be empty if
|
||||
* there were no differences between the objects.
|
||||
* </p>
|
||||
*
|
||||
* @return an unmodifiable list of {@code Diff}s
|
||||
*/
|
||||
public List<Diff<?>> getDiffs() {
|
||||
return Collections.unmodifiableList(diffs);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the number of differences between the two objects.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of differences
|
||||
*/
|
||||
public int getNumberOfDiffs() {
|
||||
return diffs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the style used by the {@link #toString()} method.
|
||||
* </p>
|
||||
*
|
||||
* @return the style
|
||||
*/
|
||||
public ToStringStyle getToStringStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a {@code String} description of the differences contained within
|
||||
* this {@code DiffResult}. A {@link ToStringBuilder} is used for each object
|
||||
* and the style of the output is governed by the {@code ToStringStyle}
|
||||
* passed to the constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If there are no differences stored in this list, the method will return
|
||||
* {@link #OBJECTS_SAME_STRING}. Otherwise, using the example given in
|
||||
* {@link Diffable} and {@link ToStringStyle#SHORT_PREFIX_STYLE}, an output
|
||||
* might be:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* Person[name=John Doe,age=32] differs from Person[name=Joe Bloggs,age=26]
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* This indicates that the objects differ in name and age, but not in
|
||||
* smoking status.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To use a different {@code ToStringStyle} for an instance of this class,
|
||||
* use {@link #toString(ToStringStyle)}.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@code String} description of the differences.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a {@code String} description of the differences contained within
|
||||
* this {@code DiffResult}, using the supplied {@code ToStringStyle}.
|
||||
* </p>
|
||||
*
|
||||
* @param style
|
||||
* the {@code ToStringStyle} to use when outputting the objects
|
||||
*
|
||||
* @return a {@code String} description of the differences.
|
||||
*/
|
||||
public String toString(final ToStringStyle style) {
|
||||
if (diffs.size() == 0) {
|
||||
return OBJECTS_SAME_STRING;
|
||||
}
|
||||
|
||||
final ToStringBuilder lhsBuilder = new ToStringBuilder(lhs, style);
|
||||
final ToStringBuilder rhsBuilder = new ToStringBuilder(rhs, style);
|
||||
|
||||
for (final Diff<?> diff : diffs) {
|
||||
lhsBuilder.append(diff.getFieldName(), diff.getLeft());
|
||||
rhsBuilder.append(diff.getFieldName(), diff.getRight());
|
||||
}
|
||||
|
||||
return String.format("%s %s %s", lhsBuilder.build(), DIFFERS_STRING,
|
||||
rhsBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns an iterator over the {@code Diff} objects contained in this list.
|
||||
* </p>
|
||||
*
|
||||
* @return the iterator
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Diff<?>> iterator() {
|
||||
return diffs.iterator();
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
/**
|
||||
* <p>{@code Diffable} classes can be compared with other objects
|
||||
* for differences. The {@link DiffResult} object retrieved can be queried
|
||||
* for a list of differences or printed using the {@link DiffResult#toString()}.</p>
|
||||
*
|
||||
* <p>The calculation of the differences is <i>consistent with equals</i> if
|
||||
* and only if {@code d1.equals(d2)} implies {@code d1.diff(d2) == ""}.
|
||||
* It is strongly recommended that implementations are consistent with equals
|
||||
* to avoid confusion. Note that {@code null} is not an instance of any class
|
||||
* and {@code d1.diff(null)} should throw a {@code NullPointerException}.</p>
|
||||
*
|
||||
* <p>
|
||||
* {@code Diffable} classes lend themselves well to unit testing, in which a
|
||||
* easily readable description of the differences between an anticipated result and
|
||||
* an actual result can be retrieved. For example:
|
||||
* </p>
|
||||
* <pre>
|
||||
* Assert.assertEquals(expected.diff(result), expected, result);
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the type of objects that this object may be differentiated against
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface Diffable<T> {
|
||||
|
||||
/**
|
||||
* <p>Retrieves a list of the differences between
|
||||
* this object and the supplied object.</p>
|
||||
*
|
||||
* @param obj the object to diff against, can be {@code null}
|
||||
* @return a list of differences
|
||||
* @throws NullPointerException if the specified object is {@code null}
|
||||
*/
|
||||
DiffResult diff(T obj);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Use this annotation to exclude a field from being being used by
|
||||
* the various <code>reflectionEquals</code> methods defined on
|
||||
* {@link EqualsBuilder}.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface EqualsExclude {
|
||||
|
||||
}
|
@ -1,993 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ArrayUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Assists in implementing {@link Object#hashCode()} methods.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class enables a good <code>hashCode</code> method to be built for any class. It follows the rules laid out in
|
||||
* the book <a href="http://www.oracle.com/technetwork/java/effectivejava-136174.html">Effective Java</a> by Joshua Bloch. Writing a
|
||||
* good <code>hashCode</code> method is actually quite difficult. This class aims to simplify the process.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The following is the approach taken. When appending a data field, the current total is multiplied by the
|
||||
* multiplier then a relevant value
|
||||
* for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then
|
||||
* appending the integer 45 will create a hash code of 674, namely 17 * 37 + 45.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* All relevant fields from the object should be included in the <code>hashCode</code> method. Derived fields may be
|
||||
* excluded. In general, any field used in the <code>equals</code> method must be used in the <code>hashCode</code>
|
||||
* method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To use this class write code as follows:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class Person {
|
||||
* String name;
|
||||
* int age;
|
||||
* boolean smoker;
|
||||
* ...
|
||||
*
|
||||
* public int hashCode() {
|
||||
* // you pick a hard-coded, randomly chosen, non-zero, odd number
|
||||
* // ideally different for each class
|
||||
* return new HashCodeBuilder(17, 37).
|
||||
* append(name).
|
||||
* append(age).
|
||||
* append(smoker).
|
||||
* toHashCode();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If required, the superclass <code>hashCode()</code> can be added using {@link #appendSuper}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are
|
||||
* usually private, the method, <code>reflectionHashCode</code>, uses <code>AccessibleObject.setAccessible</code>
|
||||
* to change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions
|
||||
* are set up correctly. It is also slower than testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A typical invocation for this method would look like:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public int hashCode() {
|
||||
* return HashCodeBuilder.reflectionHashCode(this);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>The {@link HashCodeExclude} annotation can be used to exclude fields from being
|
||||
* used by the <code>reflectionHashCode</code> methods.</p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public class HashCodeBuilder implements Builder<Integer> {
|
||||
/**
|
||||
* The default initial value to use in reflection hash code building.
|
||||
*/
|
||||
private static final int DEFAULT_INITIAL_VALUE = 17;
|
||||
|
||||
/**
|
||||
* The default multiplier value to use in reflection hash code building.
|
||||
*/
|
||||
private static final int DEFAULT_MULTIPLIER_VALUE = 37;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
private static final ThreadLocal<Set<IDKey>> REGISTRY = new ThreadLocal<>();
|
||||
|
||||
/*
|
||||
* NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
|
||||
* we are in the process of calculating.
|
||||
*
|
||||
* So we generate a one-to-one mapping from the original object to a new object.
|
||||
*
|
||||
* Now HashSet uses equals() to determine if two elements with the same hash code really
|
||||
* are equal, so we also need to ensure that the replacement objects are only equal
|
||||
* if the original objects are identical.
|
||||
*
|
||||
* The original implementation (2.4 and before) used the System.identityHashCode()
|
||||
* method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
|
||||
*
|
||||
* We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
|
||||
* to disambiguate the duplicate ids.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the registry of objects being traversed by the reflection methods in the current thread.
|
||||
* </p>
|
||||
*
|
||||
* @return Set the registry of objects being traversed
|
||||
* @since 2.3
|
||||
*/
|
||||
static Set<IDKey> getRegistry() {
|
||||
return REGISTRY.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns <code>true</code> if the registry contains the given object. Used by the reflection methods to avoid
|
||||
* infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* The object to lookup in the registry.
|
||||
* @return boolean <code>true</code> if the registry contains the given object.
|
||||
* @since 2.3
|
||||
*/
|
||||
static boolean isRegistered(final Object value) {
|
||||
final Set<IDKey> registry = getRegistry();
|
||||
return registry != null && registry.contains(new IDKey(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Appends the fields and values defined by the given object of the given <code>Class</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the object to append details of
|
||||
* @param clazz
|
||||
* the class to append details of
|
||||
* @param builder
|
||||
* the builder to append to
|
||||
* @param useTransients
|
||||
* whether to use transient fields
|
||||
* @param excludeFields
|
||||
* Collection of String field names to exclude from use in calculation of hash code
|
||||
*/
|
||||
private static void reflectionAppend(final Object object, final Class<?> clazz, final HashCodeBuilder builder, final boolean useTransients,
|
||||
final String[] excludeFields) {
|
||||
if (isRegistered(object)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
register(object);
|
||||
final Field[] fields = clazz.getDeclaredFields();
|
||||
AccessibleObject.setAccessible(fields, true);
|
||||
for (final Field field : fields) {
|
||||
if (!ArrayUtils.contains(excludeFields, field.getName())
|
||||
&& !field.getName().contains("$")
|
||||
&& (useTransients || !Modifier.isTransient(field.getModifiers()))
|
||||
&& !Modifier.isStatic(field.getModifiers())
|
||||
&& !field.isAnnotationPresent(HashCodeExclude.class)) {
|
||||
try {
|
||||
final Object fieldValue = field.get(object);
|
||||
builder.append(fieldValue);
|
||||
} catch (final IllegalAccessException e) {
|
||||
// this can't happen. Would get a Security exception instead
|
||||
// throw a runtime exception in case the impossible happens.
|
||||
throw new InternalError("Unexpected IllegalAccessException");
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unregister(object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses reflection to build a valid hash code from the fields of {@code object}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be used, as they are likely derived fields, and not part of the value of the
|
||||
* <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital. Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value. This will be the returned
|
||||
* value if no fields are found to include in the hash code
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
*
|
||||
* @see HashCodeExclude
|
||||
*/
|
||||
public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object) {
|
||||
return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses reflection to build a valid hash code from the fields of {@code object}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital. Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value. This will be the returned
|
||||
* value if no fields are found to include in the hash code
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param testTransients
|
||||
* whether to include transient fields
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
*
|
||||
* @see HashCodeExclude
|
||||
*/
|
||||
public static int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object,
|
||||
final boolean testTransients) {
|
||||
return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses reflection to build a valid hash code from the fields of {@code object}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be included. Superclass fields will be included up to and including the specified
|
||||
* superclass. A null superclass is treated as java.lang.Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital. Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object involved
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value. This will be the returned
|
||||
* value if no fields are found to include in the hash code
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param testTransients
|
||||
* whether to include transient fields
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @param excludeFields
|
||||
* array of field names to exclude from use in calculation of hash code
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
*
|
||||
* @see HashCodeExclude
|
||||
* @since 2.0
|
||||
*/
|
||||
public static <T> int reflectionHashCode(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final T object,
|
||||
final boolean testTransients, final Class<? super T> reflectUpToClass, final String... excludeFields) {
|
||||
Validate.isTrue(object != null, "The object to build a hash code for must not be null");
|
||||
final HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber);
|
||||
Class<?> clazz = object.getClass();
|
||||
reflectionAppend(object, clazz, builder, testTransients, excludeFields);
|
||||
while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
|
||||
clazz = clazz.getSuperclass();
|
||||
reflectionAppend(object, clazz, builder, testTransients, excludeFields);
|
||||
}
|
||||
return builder.toHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses reflection to build a valid hash code from the fields of {@code object}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses two hard coded choices for the constants needed to build a hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <P>
|
||||
* If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included. If no fields are found to include
|
||||
* in the hash code, the result of this method will be constant.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param testTransients
|
||||
* whether to include transient fields
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the object is <code>null</code>
|
||||
*
|
||||
* @see HashCodeExclude
|
||||
*/
|
||||
public static int reflectionHashCode(final Object object, final boolean testTransients) {
|
||||
return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object,
|
||||
testTransients, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses reflection to build a valid hash code from the fields of {@code object}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses two hard coded choices for the constants needed to build a hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be used, as they are likely derived fields, and not part of the value of the
|
||||
* <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included. If no fields are found to include
|
||||
* in the hash code, the result of this method will be constant.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param excludeFields
|
||||
* Collection of String field names to exclude from use in calculation of hash code
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the object is <code>null</code>
|
||||
*
|
||||
* @see HashCodeExclude
|
||||
*/
|
||||
public static int reflectionHashCode(final Object object, final Collection<String> excludeFields) {
|
||||
return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses reflection to build a valid hash code from the fields of {@code object}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses two hard coded choices for the constants needed to build a hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be used, as they are likely derived fields, and not part of the value of the
|
||||
* <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included. If no fields are found to include
|
||||
* in the hash code, the result of this method will be constant.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param excludeFields
|
||||
* array of field names to exclude from use in calculation of hash code
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the object is <code>null</code>
|
||||
*
|
||||
* @see HashCodeExclude
|
||||
*/
|
||||
public static int reflectionHashCode(final Object object, final String... excludeFields) {
|
||||
return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object, false,
|
||||
null, excludeFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Registers the given object. Used by the reflection methods to avoid infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* The object to register.
|
||||
*/
|
||||
private static void register(final Object value) {
|
||||
Set<IDKey> registry = getRegistry();
|
||||
if (registry == null) {
|
||||
registry = new HashSet<>();
|
||||
REGISTRY.set(registry);
|
||||
}
|
||||
registry.add(new IDKey(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Unregisters the given object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Used by the reflection methods to avoid infinite loops.
|
||||
*
|
||||
* @param value
|
||||
* The object to unregister.
|
||||
* @since 2.3
|
||||
*/
|
||||
private static void unregister(final Object value) {
|
||||
final Set<IDKey> registry = getRegistry();
|
||||
if (registry != null) {
|
||||
registry.remove(new IDKey(value));
|
||||
if (registry.isEmpty()) {
|
||||
REGISTRY.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant to use in building the hashCode.
|
||||
*/
|
||||
private final int iConstant;
|
||||
|
||||
/**
|
||||
* Running total of the hashCode.
|
||||
*/
|
||||
private int iTotal = 0;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses two hard coded choices for the constants needed to build a <code>hashCode</code>.
|
||||
* </p>
|
||||
*/
|
||||
public HashCodeBuilder() {
|
||||
iConstant = 37;
|
||||
iTotal = 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Two randomly chosen, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param initialOddNumber
|
||||
* an odd number used as the initial value
|
||||
* @param multiplierOddNumber
|
||||
* an odd number used as the multiplier
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is even
|
||||
*/
|
||||
public HashCodeBuilder(final int initialOddNumber, final int multiplierOddNumber) {
|
||||
Validate.isTrue(initialOddNumber % 2 != 0, "HashCodeBuilder requires an odd initial value");
|
||||
Validate.isTrue(multiplierOddNumber % 2 != 0, "HashCodeBuilder requires an odd multiplier");
|
||||
iConstant = multiplierOddNumber;
|
||||
iTotal = initialOddNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>boolean</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This adds <code>1</code> when true, and <code>0</code> when false to the <code>hashCode</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is in contrast to the standard <code>java.lang.Boolean.hashCode</code> handling, which computes
|
||||
* a <code>hashCode</code> value of <code>1231</code> for <code>java.lang.Boolean</code> instances
|
||||
* that represent <code>true</code> or <code>1237</code> for <code>java.lang.Boolean</code> instances
|
||||
* that represent <code>false</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is in accordance with the <i>Effective Java</i> design.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the boolean to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final boolean value) {
|
||||
iTotal = iTotal * iConstant + (value ? 0 : 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>boolean</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final boolean[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final boolean element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>byte</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the byte to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final byte value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>byte</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final byte[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final byte element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>char</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the char to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final char value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>char</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final char[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final char element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>double</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the double to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final double value) {
|
||||
return append(Double.doubleToLongBits(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>double</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final double[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final double element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>float</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the float to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final float value) {
|
||||
iTotal = iTotal * iConstant + Float.floatToIntBits(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>float</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final float[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final float element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>int</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the int to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final int value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>int</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final int[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final int element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>long</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the long to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
// NOTE: This method uses >> and not >>> as Effective Java and
|
||||
// Long.hashCode do. Ideally we should switch to >>> at
|
||||
// some stage. There are backwards compat issues, so
|
||||
// that will have to wait for the time being. cf LANG-342.
|
||||
public HashCodeBuilder append(final long value) {
|
||||
iTotal = iTotal * iConstant + ((int) (value ^ (value >> 32)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>long</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final long[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final long element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final Object object) {
|
||||
if (object == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
|
||||
} else {
|
||||
if (object.getClass().isArray()) {
|
||||
// factor out array case in order to keep method small enough
|
||||
// to be inlined
|
||||
appendArray(object);
|
||||
} else {
|
||||
iTotal = iTotal * iConstant + object.hashCode();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an array.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the array to add to the <code>hashCode</code>
|
||||
*/
|
||||
private void appendArray(final Object object) {
|
||||
// 'Switch' on type of array, to dispatch to the correct handler
|
||||
// This handles multi dimensional arrays
|
||||
if (object instanceof long[]) {
|
||||
append((long[]) object);
|
||||
} else if (object instanceof int[]) {
|
||||
append((int[]) object);
|
||||
} else if (object instanceof short[]) {
|
||||
append((short[]) object);
|
||||
} else if (object instanceof char[]) {
|
||||
append((char[]) object);
|
||||
} else if (object instanceof byte[]) {
|
||||
append((byte[]) object);
|
||||
} else if (object instanceof double[]) {
|
||||
append((double[]) object);
|
||||
} else if (object instanceof float[]) {
|
||||
append((float[]) object);
|
||||
} else if (object instanceof boolean[]) {
|
||||
append((boolean[]) object);
|
||||
} else {
|
||||
// Not an array of primitives
|
||||
append((Object[]) object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>Object</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final Object[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final Object element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>short</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the short to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final short value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>short</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(final short[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (final short element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Adds the result of super.hashCode() to this builder.
|
||||
* </p>
|
||||
*
|
||||
* @param superHashCode
|
||||
* the result of calling <code>super.hashCode()</code>
|
||||
* @return this HashCodeBuilder, used to chain calls.
|
||||
* @since 2.0
|
||||
*/
|
||||
public HashCodeBuilder appendSuper(final int superHashCode) {
|
||||
iTotal = iTotal * iConstant + superHashCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Return the computed <code>hashCode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return <code>hashCode</code> based on the fields appended
|
||||
*/
|
||||
public int toHashCode() {
|
||||
return iTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the computed <code>hashCode</code>.
|
||||
*
|
||||
* @return <code>hashCode</code> based on the fields appended
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@Override
|
||||
public Integer build() {
|
||||
return Integer.valueOf(toHashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The computed <code>hashCode</code> from toHashCode() is returned due to the likelihood
|
||||
* of bugs in mis-calling toHashCode() and the unlikeliness of it mattering what the hashCode for
|
||||
* HashCodeBuilder itself is.</p>
|
||||
*
|
||||
* @return <code>hashCode</code> based on the fields appended
|
||||
* @since 2.5
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toHashCode();
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Use this annotation to exclude a field from being being used by
|
||||
* the various <code>reflectionHashcode</code> methods defined on
|
||||
* {@link HashCodeBuilder}.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface HashCodeExclude {
|
||||
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
// adapted from org.apache.axis.utils.IDKey
|
||||
|
||||
/**
|
||||
* Wrap an identity key (System.identityHashCode())
|
||||
* so that an object can only be equal() to itself.
|
||||
*
|
||||
* This is necessary to disambiguate the occasional duplicate
|
||||
* identityHashCodes that can occur.
|
||||
*/
|
||||
final class IDKey {
|
||||
private final Object value;
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* Constructor for IDKey
|
||||
* @param _value The value
|
||||
*/
|
||||
IDKey(final Object _value) {
|
||||
// This is the Object hash code
|
||||
id = System.identityHashCode(_value);
|
||||
// There have been some cases (LANG-459) that return the
|
||||
// same identity hash code for different objects. So
|
||||
// the value is also added to disambiguate these cases.
|
||||
value = _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns hash code - i.e. the system identity hashcode.
|
||||
* @return the hashcode
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if instances are equal
|
||||
* @param other The other object to compare to
|
||||
* @return if the instances are for the same object
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (!(other instanceof IDKey)) {
|
||||
return false;
|
||||
}
|
||||
final IDKey idKey = (IDKey) other;
|
||||
if (id != idKey.id) {
|
||||
return false;
|
||||
}
|
||||
// Note that identity equals is used.
|
||||
return value == idKey.value;
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
|
||||
/**
|
||||
* <p>Works with {@link ToStringBuilder} to create a "deep" <code>toString</code>.</p>
|
||||
*
|
||||
* <p>To use this class write code as follows:</p>
|
||||
*
|
||||
* <pre>
|
||||
* public class Job {
|
||||
* String title;
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* public class Person {
|
||||
* String name;
|
||||
* int age;
|
||||
* boolean smoker;
|
||||
* Job job;
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* public String toString() {
|
||||
* return new ReflectionToStringBuilder(this, new RecursiveToStringStyle()).toString();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>This will produce a toString of the format:
|
||||
* <code>Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]</code></p>
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public class RecursiveToStringStyle extends ToStringStyle {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <p>Constructor.</p>
|
||||
*/
|
||||
public RecursiveToStringStyle() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
|
||||
if (!ClassUtils.isPrimitiveWrapper(value.getClass()) &&
|
||||
!String.class.equals(value.getClass()) &&
|
||||
accept(value.getClass())) {
|
||||
buffer.append(ReflectionToStringBuilder.toString(value, this));
|
||||
} else {
|
||||
super.appendDetail(buffer, fieldName, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll) {
|
||||
appendClassName(buffer, coll);
|
||||
appendIdentityHashCode(buffer, coll);
|
||||
appendDetail(buffer, fieldName, coll.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not to recursively format the given <code>Class</code>.
|
||||
* By default, this method always returns {@code true}, but may be overwritten by
|
||||
* sub-classes to filter specific classes.
|
||||
*
|
||||
* @param clazz
|
||||
* The class to test.
|
||||
* @return Whether or not to recursively format the given <code>Class</code>.
|
||||
*/
|
||||
protected boolean accept(final Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,846 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ArrayUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Assists in implementing {@link Object#toString()} methods using reflection.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class uses reflection to determine the fields to append. Because these fields are usually private, the class
|
||||
* uses {@link AccessibleObject#setAccessible(AccessibleObject[], boolean)} to
|
||||
* change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are
|
||||
* set up correctly.
|
||||
* </p>
|
||||
* <p>
|
||||
* Using reflection to access (private) fields circumvents any synchronization protection guarding access to these
|
||||
* fields. If a toString method cannot safely read a field, you should exclude it from the toString method, or use
|
||||
* synchronization consistent with the class' lock management around the invocation of the method. Take special care to
|
||||
* exclude non-thread-safe collection classes, because these classes may throw ConcurrentModificationException if
|
||||
* modified while the toString method is executing.
|
||||
* </p>
|
||||
* <p>
|
||||
* A typical invocation for this method would look like:
|
||||
* </p>
|
||||
* <pre>
|
||||
* public String toString() {
|
||||
* return ReflectionToStringBuilder.toString(this);
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* You can also use the builder to debug 3rd party objects:
|
||||
* </p>
|
||||
* <pre>
|
||||
* System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
|
||||
* </pre>
|
||||
* <p>
|
||||
* A subclass can control field output by overriding the methods:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>{@link #accept(Field)}</li>
|
||||
* <li>{@link #getValue(Field)}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* For example, this method does <i>not</i> include the <code>password</code> field in the returned <code>String</code>:
|
||||
* </p>
|
||||
* <pre>
|
||||
* public String toString() {
|
||||
* return (new ReflectionToStringBuilder(this) {
|
||||
* protected boolean accept(Field f) {
|
||||
* return super.accept(f) && !f.getName().equals("password");
|
||||
* }
|
||||
* }).toString();
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Alternatively the {@link ToStringExclude} annotation can be used to exclude fields from being incorporated in the
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* The exact format of the <code>toString</code> is determined by the {@link ToStringStyle} passed into the constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b> the default {@link ToStringStyle} will only do a "shallow" formatting, i.e. composed objects are not
|
||||
* further traversed. To get "deep" formatting, use an instance of {@link RecursiveToStringStyle}.
|
||||
* </p>
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReflectionToStringBuilder extends ToStringBuilder {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value using the default <code>ToStringStyle</code> through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
|
||||
* Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*
|
||||
* @see ToStringExclude
|
||||
*/
|
||||
public static String toString(final Object object) {
|
||||
return toString(object, null, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
|
||||
* Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object or <code>ToStringStyle</code> is <code>null</code>
|
||||
*
|
||||
* @see ToStringExclude
|
||||
*/
|
||||
public static String toString(final Object object, final ToStringStyle style) {
|
||||
return toString(object, style, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient members will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be included. Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*
|
||||
* @see ToStringExclude
|
||||
*/
|
||||
public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients) {
|
||||
return toString(object, style, outputTransients, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
|
||||
* ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be included. Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*
|
||||
* @see ToStringExclude
|
||||
* @since 2.1
|
||||
*/
|
||||
public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients, final boolean outputStatics) {
|
||||
return toString(object, style, outputTransients, outputStatics, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
|
||||
* ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
|
||||
* <code>java.lang.Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*
|
||||
* @see ToStringExclude
|
||||
* @since 2.1
|
||||
*/
|
||||
public static <T> String toString(
|
||||
final T object, final ToStringStyle style, final boolean outputTransients,
|
||||
final boolean outputStatics, final Class<? super T> reflectUpToClass) {
|
||||
return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
|
||||
* ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
|
||||
* <code>java.lang.Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @param excludeNullValues
|
||||
* whether to exclude fields whose values are null
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*
|
||||
* @see ToStringExclude
|
||||
* @since 3.6
|
||||
*/
|
||||
public static <T> String toString(
|
||||
final T object, final ToStringStyle style, final boolean outputTransients,
|
||||
final boolean outputStatics, boolean excludeNullValues, final Class<? super T> reflectUpToClass) {
|
||||
return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics, excludeNullValues)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a String for a toString method excluding the given field names.
|
||||
*
|
||||
* @param object
|
||||
* The object to "toString".
|
||||
* @param excludeFieldNames
|
||||
* The field names to exclude. Null excludes nothing.
|
||||
* @return The toString value.
|
||||
*/
|
||||
public static String toStringExclude(final Object object, final Collection<String> excludeFieldNames) {
|
||||
return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Collection into an array of Strings. The returned array does not contain <code>null</code>
|
||||
* entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
|
||||
* is <code>null</code>.
|
||||
*
|
||||
* @param collection
|
||||
* The collection to convert
|
||||
* @return A new array of Strings.
|
||||
*/
|
||||
static String[] toNoNullStringArray(final Collection<String> collection) {
|
||||
if (collection == null) {
|
||||
return ArrayUtils.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
return toNoNullStringArray(collection.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new array of Strings without null elements. Internal method used to normalize exclude lists
|
||||
* (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException}
|
||||
* if an array element is <code>null</code>.
|
||||
*
|
||||
* @param array
|
||||
* The array to check
|
||||
* @return The given array or a new array without null.
|
||||
*/
|
||||
static String[] toNoNullStringArray(final Object[] array) {
|
||||
final List<String> list = new ArrayList<>(array.length);
|
||||
for (final Object e : array) {
|
||||
if (e != null) {
|
||||
list.add(e.toString());
|
||||
}
|
||||
}
|
||||
return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds a String for a toString method excluding the given field names.
|
||||
*
|
||||
* @param object
|
||||
* The object to "toString".
|
||||
* @param excludeFieldNames
|
||||
* The field names to exclude
|
||||
* @return The toString value.
|
||||
*/
|
||||
public static String toStringExclude(final Object object, final String... excludeFieldNames) {
|
||||
return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
|
||||
}
|
||||
|
||||
private static Object checkNotNull(final Object obj) {
|
||||
Validate.isTrue(obj != null, "The Object passed in should not be null.");
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to append static fields.
|
||||
*/
|
||||
private boolean appendStatics = false;
|
||||
|
||||
/**
|
||||
* Whether or not to append transient fields.
|
||||
*/
|
||||
private boolean appendTransients = false;
|
||||
|
||||
/**
|
||||
* Whether or not to append fields that are null.
|
||||
*/
|
||||
private boolean excludeNullValues;
|
||||
|
||||
/**
|
||||
* Which field names to exclude from output. Intended for fields like <code>"password"</code>.
|
||||
*
|
||||
* @since 3.0 this is protected instead of private
|
||||
*/
|
||||
protected String[] excludeFieldNames;
|
||||
|
||||
/**
|
||||
* The last super class to stop appending fields for.
|
||||
*/
|
||||
private Class<?> upToClass = null;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor outputs using the default style set with <code>setDefaultStyle</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for, must not be <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object passed in is <code>null</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder(final Object object) {
|
||||
super(checkNotNull(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default style is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for, must not be <code>null</code>
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object passed in is <code>null</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder(final Object object, final ToStringStyle style) {
|
||||
super(checkNotNull(object), style);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default style is used.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the buffer is <code>null</code>, a new one is created.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param buffer
|
||||
* the <code>StringBuffer</code> to populate, may be <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object passed in is <code>null</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder(final Object object, final ToStringStyle style, final StringBuffer buffer) {
|
||||
super(checkNotNull(object), style, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param buffer
|
||||
* the <code>StringBuffer</code> to populate, may be <code>null</code>
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @since 2.1
|
||||
*/
|
||||
public <T> ReflectionToStringBuilder(
|
||||
final T object, final ToStringStyle style, final StringBuffer buffer,
|
||||
final Class<? super T> reflectUpToClass, final boolean outputTransients, final boolean outputStatics) {
|
||||
super(checkNotNull(object), style, buffer);
|
||||
this.setUpToClass(reflectUpToClass);
|
||||
this.setAppendTransients(outputTransients);
|
||||
this.setAppendStatics(outputStatics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param buffer
|
||||
* the <code>StringBuffer</code> to populate, may be <code>null</code>
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @param excludeNullValues
|
||||
* whether to exclude fields who value is null
|
||||
* @since 3.6
|
||||
*/
|
||||
public <T> ReflectionToStringBuilder(
|
||||
final T object, final ToStringStyle style, final StringBuffer buffer,
|
||||
final Class<? super T> reflectUpToClass, final boolean outputTransients, final boolean outputStatics,
|
||||
final boolean excludeNullValues) {
|
||||
super(checkNotNull(object), style, buffer);
|
||||
this.setUpToClass(reflectUpToClass);
|
||||
this.setAppendTransients(outputTransients);
|
||||
this.setAppendStatics(outputStatics);
|
||||
this.setExcludeNullValues(excludeNullValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not to append the given <code>Field</code>.
|
||||
* <ul>
|
||||
* <li>Transient fields are appended only if {@link #isAppendTransients()} returns <code>true</code>.
|
||||
* <li>Static fields are appended only if {@link #isAppendStatics()} returns <code>true</code>.
|
||||
* <li>Inner class fields are not appended.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param field
|
||||
* The Field to test.
|
||||
* @return Whether or not to append the given <code>Field</code>.
|
||||
*/
|
||||
protected boolean accept(final Field field) {
|
||||
if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
|
||||
// Reject field from inner class.
|
||||
return false;
|
||||
}
|
||||
if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) {
|
||||
// Reject transient fields.
|
||||
return false;
|
||||
}
|
||||
if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) {
|
||||
// Reject static fields.
|
||||
return false;
|
||||
}
|
||||
if (this.excludeFieldNames != null
|
||||
&& Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
|
||||
// Reject fields from the getExcludeFieldNames list.
|
||||
return false;
|
||||
}
|
||||
return !field.isAnnotationPresent(ToStringExclude.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Appends the fields and values defined by the given object of the given Class.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If a cycle is detected as an object is "toString()'ed", such an object is rendered as if
|
||||
* <code>Object.toString()</code> had been called and not implemented by the object.
|
||||
* </p>
|
||||
*
|
||||
* @param clazz
|
||||
* The class of object parameter
|
||||
*/
|
||||
protected void appendFieldsIn(final Class<?> clazz) {
|
||||
if (clazz.isArray()) {
|
||||
this.reflectionAppendArray(this.getObject());
|
||||
return;
|
||||
}
|
||||
final Field[] fields = clazz.getDeclaredFields();
|
||||
AccessibleObject.setAccessible(fields, true);
|
||||
for (final Field field : fields) {
|
||||
final String fieldName = field.getName();
|
||||
if (this.accept(field)) {
|
||||
try {
|
||||
// Warning: Field.get(Object) creates wrappers objects
|
||||
// for primitive types.
|
||||
final Object fieldValue = this.getValue(field);
|
||||
if (!excludeNullValues || fieldValue != null) {
|
||||
this.append(fieldName, fieldValue);
|
||||
}
|
||||
} catch (final IllegalAccessException ex) {
|
||||
//this can't happen. Would get a Security exception
|
||||
// instead
|
||||
//throw a runtime exception in case the impossible
|
||||
// happens.
|
||||
throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the excludeFieldNames.
|
||||
*/
|
||||
public String[] getExcludeFieldNames() {
|
||||
return this.excludeFieldNames.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the last super class to stop appending fields for.
|
||||
* </p>
|
||||
*
|
||||
* @return The last super class to stop appending fields for.
|
||||
*/
|
||||
public Class<?> getUpToClass() {
|
||||
return this.upToClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calls <code>java.lang.reflect.Field.get(Object)</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param field
|
||||
* The Field to query.
|
||||
* @return The Object from the given Field.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* see {@link Field#get(Object)}
|
||||
* @throws IllegalAccessException
|
||||
* see {@link Field#get(Object)}
|
||||
*
|
||||
* @see Field#get(Object)
|
||||
*/
|
||||
protected Object getValue(final Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
return field.get(this.getObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets whether or not to append static fields.
|
||||
* </p>
|
||||
*
|
||||
* @return Whether or not to append static fields.
|
||||
* @since 2.1
|
||||
*/
|
||||
public boolean isAppendStatics() {
|
||||
return this.appendStatics;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets whether or not to append transient fields.
|
||||
* </p>
|
||||
*
|
||||
* @return Whether or not to append transient fields.
|
||||
*/
|
||||
public boolean isAppendTransients() {
|
||||
return this.appendTransients;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets whether or not to append fields whose values are null.
|
||||
* </p>
|
||||
*
|
||||
* @return Whether or not to append fields whose values are null.
|
||||
* @since 3.6
|
||||
*/
|
||||
public boolean isExcludeNullValues() {
|
||||
return this.excludeNullValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append to the <code>toString</code> an <code>Object</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>toString</code>
|
||||
* @return this
|
||||
*/
|
||||
public ReflectionToStringBuilder reflectionAppendArray(final Object array) {
|
||||
this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets whether or not to append static fields.
|
||||
* </p>
|
||||
*
|
||||
* @param appendStatics
|
||||
* Whether or not to append static fields.
|
||||
* @since 2.1
|
||||
*/
|
||||
public void setAppendStatics(final boolean appendStatics) {
|
||||
this.appendStatics = appendStatics;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets whether or not to append transient fields.
|
||||
* </p>
|
||||
*
|
||||
* @param appendTransients
|
||||
* Whether or not to append transient fields.
|
||||
*/
|
||||
public void setAppendTransients(final boolean appendTransients) {
|
||||
this.appendTransients = appendTransients;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets whether or not to append fields whose values are null.
|
||||
* </p>
|
||||
*
|
||||
* @param excludeNullValues
|
||||
* Whether or not to append fields whose values are null.
|
||||
* @since 3.6
|
||||
*/
|
||||
public void setExcludeNullValues(final boolean excludeNullValues) {
|
||||
this.excludeNullValues = excludeNullValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the field names to exclude.
|
||||
*
|
||||
* @param excludeFieldNamesParam
|
||||
* The excludeFieldNames to excluding from toString or <code>null</code>.
|
||||
* @return <code>this</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder setExcludeFieldNames(final String... excludeFieldNamesParam) {
|
||||
if (excludeFieldNamesParam == null) {
|
||||
this.excludeFieldNames = null;
|
||||
} else {
|
||||
//clone and remove nulls
|
||||
this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
|
||||
Arrays.sort(this.excludeFieldNames);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the last super class to stop appending fields for.
|
||||
* </p>
|
||||
*
|
||||
* @param clazz
|
||||
* The last super class to stop appending fields for.
|
||||
*/
|
||||
public void setUpToClass(final Class<?> clazz) {
|
||||
if (clazz != null) {
|
||||
final Object object = getObject();
|
||||
if (object != null && clazz.isInstance(object) == false) {
|
||||
throw new IllegalArgumentException("Specified class is not a superclass of the object");
|
||||
}
|
||||
}
|
||||
this.upToClass = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the String built by this builder.
|
||||
* </p>
|
||||
*
|
||||
* @return the built string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.getObject() == null) {
|
||||
return this.getStyle().getNullText();
|
||||
}
|
||||
Class<?> clazz = this.getObject().getClass();
|
||||
this.appendFieldsIn(clazz);
|
||||
while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
|
||||
clazz = clazz.getSuperclass();
|
||||
this.appendFieldsIn(clazz);
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
}
|
@ -1,559 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
/**
|
||||
* <p>Works with {@link ToStringBuilder} to create a <code>toString</code>.</p>
|
||||
*
|
||||
* <p>This class is intended to be used as a singleton.
|
||||
* There is no need to instantiate a new style each time.
|
||||
* Simply instantiate the class once, customize the values as required, and
|
||||
* store the result in a public static final variable for the rest of the
|
||||
* program to access.</p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public class StandardToStringStyle extends ToStringStyle {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <p>Constructor.</p>
|
||||
*/
|
||||
public StandardToStringStyle() {
|
||||
super();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether to use the class name.</p>
|
||||
*
|
||||
* @return the current useClassName flag
|
||||
*/
|
||||
@Override
|
||||
public boolean isUseClassName() { // NOPMD as this is implementing the abstract class
|
||||
return super.isUseClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether to use the class name.</p>
|
||||
*
|
||||
* @param useClassName the new useClassName flag
|
||||
*/
|
||||
@Override
|
||||
public void setUseClassName(final boolean useClassName) { // NOPMD as this is implementing the abstract class
|
||||
super.setUseClassName(useClassName);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether to output short or long class names.</p>
|
||||
*
|
||||
* @return the current useShortClassName flag
|
||||
* @since 2.0
|
||||
*/
|
||||
@Override
|
||||
public boolean isUseShortClassName() { // NOPMD as this is implementing the abstract class
|
||||
return super.isUseShortClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether to output short or long class names.</p>
|
||||
*
|
||||
* @param useShortClassName the new useShortClassName flag
|
||||
* @since 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setUseShortClassName(final boolean useShortClassName) { // NOPMD as this is implementing the abstract class
|
||||
super.setUseShortClassName(useShortClassName);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether to use the identity hash code.</p>
|
||||
* @return the current useIdentityHashCode flag
|
||||
*/
|
||||
@Override
|
||||
public boolean isUseIdentityHashCode() { // NOPMD as this is implementing the abstract class
|
||||
return super.isUseIdentityHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether to use the identity hash code.</p>
|
||||
*
|
||||
* @param useIdentityHashCode the new useIdentityHashCode flag
|
||||
*/
|
||||
@Override
|
||||
public void setUseIdentityHashCode(final boolean useIdentityHashCode) { // NOPMD as this is implementing the abstract class
|
||||
super.setUseIdentityHashCode(useIdentityHashCode);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether to use the field names passed in.</p>
|
||||
*
|
||||
* @return the current useFieldNames flag
|
||||
*/
|
||||
@Override
|
||||
public boolean isUseFieldNames() { // NOPMD as this is implementing the abstract class
|
||||
return super.isUseFieldNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether to use the field names passed in.</p>
|
||||
*
|
||||
* @param useFieldNames the new useFieldNames flag
|
||||
*/
|
||||
@Override
|
||||
public void setUseFieldNames(final boolean useFieldNames) { // NOPMD as this is implementing the abstract class
|
||||
super.setUseFieldNames(useFieldNames);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether to use full detail when the caller doesn't
|
||||
* specify.</p>
|
||||
*
|
||||
* @return the current defaultFullDetail flag
|
||||
*/
|
||||
@Override
|
||||
public boolean isDefaultFullDetail() { // NOPMD as this is implementing the abstract class
|
||||
return super.isDefaultFullDetail();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether to use full detail when the caller doesn't
|
||||
* specify.</p>
|
||||
*
|
||||
* @param defaultFullDetail the new defaultFullDetail flag
|
||||
*/
|
||||
@Override
|
||||
public void setDefaultFullDetail(final boolean defaultFullDetail) { // NOPMD as this is implementing the abstract class
|
||||
super.setDefaultFullDetail(defaultFullDetail);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether to output array content detail.</p>
|
||||
*
|
||||
* @return the current array content detail setting
|
||||
*/
|
||||
@Override
|
||||
public boolean isArrayContentDetail() { // NOPMD as this is implementing the abstract class
|
||||
return super.isArrayContentDetail();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether to output array content detail.</p>
|
||||
*
|
||||
* @param arrayContentDetail the new arrayContentDetail flag
|
||||
*/
|
||||
@Override
|
||||
public void setArrayContentDetail(final boolean arrayContentDetail) { // NOPMD as this is implementing the abstract class
|
||||
super.setArrayContentDetail(arrayContentDetail);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the array start text.</p>
|
||||
*
|
||||
* @return the current array start text
|
||||
*/
|
||||
@Override
|
||||
public String getArrayStart() { // NOPMD as this is implementing the abstract class
|
||||
return super.getArrayStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the array start text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param arrayStart the new array start text
|
||||
*/
|
||||
@Override
|
||||
public void setArrayStart(final String arrayStart) { // NOPMD as this is implementing the abstract class
|
||||
super.setArrayStart(arrayStart);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the array end text.</p>
|
||||
*
|
||||
* @return the current array end text
|
||||
*/
|
||||
@Override
|
||||
public String getArrayEnd() { // NOPMD as this is implementing the abstract class
|
||||
return super.getArrayEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the array end text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param arrayEnd the new array end text
|
||||
*/
|
||||
@Override
|
||||
public void setArrayEnd(final String arrayEnd) { // NOPMD as this is implementing the abstract class
|
||||
super.setArrayEnd(arrayEnd);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the array separator text.</p>
|
||||
*
|
||||
* @return the current array separator text
|
||||
*/
|
||||
@Override
|
||||
public String getArraySeparator() { // NOPMD as this is implementing the abstract class
|
||||
return super.getArraySeparator();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the array separator text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param arraySeparator the new array separator text
|
||||
*/
|
||||
@Override
|
||||
public void setArraySeparator(final String arraySeparator) { // NOPMD as this is implementing the abstract class
|
||||
super.setArraySeparator(arraySeparator);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the content start text.</p>
|
||||
*
|
||||
* @return the current content start text
|
||||
*/
|
||||
@Override
|
||||
public String getContentStart() { // NOPMD as this is implementing the abstract class
|
||||
return super.getContentStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the content start text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param contentStart the new content start text
|
||||
*/
|
||||
@Override
|
||||
public void setContentStart(final String contentStart) { // NOPMD as this is implementing the abstract class
|
||||
super.setContentStart(contentStart);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the content end text.</p>
|
||||
*
|
||||
* @return the current content end text
|
||||
*/
|
||||
@Override
|
||||
public String getContentEnd() { // NOPMD as this is implementing the abstract class
|
||||
return super.getContentEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the content end text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param contentEnd the new content end text
|
||||
*/
|
||||
@Override
|
||||
public void setContentEnd(final String contentEnd) { // NOPMD as this is implementing the abstract class
|
||||
super.setContentEnd(contentEnd);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the field name value separator text.</p>
|
||||
*
|
||||
* @return the current field name value separator text
|
||||
*/
|
||||
@Override
|
||||
public String getFieldNameValueSeparator() { // NOPMD as this is implementing the abstract class
|
||||
return super.getFieldNameValueSeparator();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the field name value separator text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param fieldNameValueSeparator the new field name value separator text
|
||||
*/
|
||||
@Override
|
||||
public void setFieldNameValueSeparator(final String fieldNameValueSeparator) { // NOPMD as this is implementing the abstract class
|
||||
super.setFieldNameValueSeparator(fieldNameValueSeparator);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the field separator text.</p>
|
||||
*
|
||||
* @return the current field separator text
|
||||
*/
|
||||
@Override
|
||||
public String getFieldSeparator() { // NOPMD as this is implementing the abstract class
|
||||
return super.getFieldSeparator();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the field separator text.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param fieldSeparator the new field separator text
|
||||
*/
|
||||
@Override
|
||||
public void setFieldSeparator(final String fieldSeparator) { // NOPMD as this is implementing the abstract class
|
||||
super.setFieldSeparator(fieldSeparator);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether the field separator should be added at the start
|
||||
* of each buffer.</p>
|
||||
*
|
||||
* @return the fieldSeparatorAtStart flag
|
||||
* @since 2.0
|
||||
*/
|
||||
@Override
|
||||
public boolean isFieldSeparatorAtStart() { // NOPMD as this is implementing the abstract class
|
||||
return super.isFieldSeparatorAtStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether the field separator should be added at the start
|
||||
* of each buffer.</p>
|
||||
*
|
||||
* @param fieldSeparatorAtStart the fieldSeparatorAtStart flag
|
||||
* @since 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) { // NOPMD as this is implementing the abstract class
|
||||
super.setFieldSeparatorAtStart(fieldSeparatorAtStart);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets whether the field separator should be added at the end
|
||||
* of each buffer.</p>
|
||||
*
|
||||
* @return fieldSeparatorAtEnd flag
|
||||
* @since 2.0
|
||||
*/
|
||||
@Override
|
||||
public boolean isFieldSeparatorAtEnd() { // NOPMD as this is implementing the abstract class
|
||||
return super.isFieldSeparatorAtEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether the field separator should be added at the end
|
||||
* of each buffer.</p>
|
||||
*
|
||||
* @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag
|
||||
* @since 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) { // NOPMD as this is implementing the abstract class
|
||||
super.setFieldSeparatorAtEnd(fieldSeparatorAtEnd);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the text to output when <code>null</code> found.</p>
|
||||
*
|
||||
* @return the current text to output when <code>null</code> found
|
||||
*/
|
||||
@Override
|
||||
public String getNullText() { // NOPMD as this is implementing the abstract class
|
||||
return super.getNullText();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the text to output when <code>null</code> found.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param nullText the new text to output when <code>null</code> found
|
||||
*/
|
||||
@Override
|
||||
public void setNullText(final String nullText) { // NOPMD as this is implementing the abstract class
|
||||
super.setNullText(nullText);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the text to output when a <code>Collection</code>,
|
||||
* <code>Map</code> or <code>Array</code> size is output.</p>
|
||||
*
|
||||
* <p>This is output before the size value.</p>
|
||||
*
|
||||
* @return the current start of size text
|
||||
*/
|
||||
@Override
|
||||
public String getSizeStartText() { // NOPMD as this is implementing the abstract class
|
||||
return super.getSizeStartText();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the start text to output when a <code>Collection</code>,
|
||||
* <code>Map</code> or <code>Array</code> size is output.</p>
|
||||
*
|
||||
* <p>This is output before the size value.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted to
|
||||
* an empty String.</p>
|
||||
*
|
||||
* @param sizeStartText the new start of size text
|
||||
*/
|
||||
@Override
|
||||
public void setSizeStartText(final String sizeStartText) { // NOPMD as this is implementing the abstract class
|
||||
super.setSizeStartText(sizeStartText);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the end text to output when a <code>Collection</code>,
|
||||
* <code>Map</code> or <code>Array</code> size is output.</p>
|
||||
*
|
||||
* <p>This is output after the size value.</p>
|
||||
*
|
||||
* @return the current end of size text
|
||||
*/
|
||||
@Override
|
||||
public String getSizeEndText() { // NOPMD as this is implementing the abstract class
|
||||
return super.getSizeEndText();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the end text to output when a <code>Collection</code>,
|
||||
* <code>Map</code> or <code>Array</code> size is output.</p>
|
||||
*
|
||||
* <p>This is output after the size value.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted
|
||||
* to an empty String.</p>
|
||||
*
|
||||
* @param sizeEndText the new end of size text
|
||||
*/
|
||||
@Override
|
||||
public void setSizeEndText(final String sizeEndText) { // NOPMD as this is implementing the abstract class
|
||||
super.setSizeEndText(sizeEndText);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the start text to output when an <code>Object</code> is
|
||||
* output in summary mode.</p>
|
||||
*
|
||||
* <P>This is output before the size value.</p>
|
||||
*
|
||||
* @return the current start of summary text
|
||||
*/
|
||||
@Override
|
||||
public String getSummaryObjectStartText() { // NOPMD as this is implementing the abstract class
|
||||
return super.getSummaryObjectStartText();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the start text to output when an <code>Object</code> is
|
||||
* output in summary mode.</p>
|
||||
*
|
||||
* <p>This is output before the size value.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted to
|
||||
* an empty String.</p>
|
||||
*
|
||||
* @param summaryObjectStartText the new start of summary text
|
||||
*/
|
||||
@Override
|
||||
public void setSummaryObjectStartText(final String summaryObjectStartText) { // NOPMD as this is implementing the abstract class
|
||||
super.setSummaryObjectStartText(summaryObjectStartText);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Gets the end text to output when an <code>Object</code> is
|
||||
* output in summary mode.</p>
|
||||
*
|
||||
* <p>This is output after the size value.</p>
|
||||
*
|
||||
* @return the current end of summary text
|
||||
*/
|
||||
@Override
|
||||
public String getSummaryObjectEndText() { // NOPMD as this is implementing the abstract class
|
||||
return super.getSummaryObjectEndText();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the end text to output when an <code>Object</code> is
|
||||
* output in summary mode.</p>
|
||||
*
|
||||
* <p>This is output after the size value.</p>
|
||||
*
|
||||
* <p><code>null</code> is accepted, but will be converted to
|
||||
* an empty String.</p>
|
||||
*
|
||||
* @param summaryObjectEndText the new end of summary text
|
||||
*/
|
||||
@Override
|
||||
public void setSummaryObjectEndText(final String summaryObjectEndText) { // NOPMD as this is implementing the abstract class
|
||||
super.setSummaryObjectEndText(summaryObjectEndText);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.builder;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Use this annotation to exclude a field from being being used by
|
||||
* the {@link ReflectionToStringBuilder}.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface ToStringExclude {
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.concurrent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* An exception class used for reporting error conditions related to accessing
|
||||
* data of background tasks.
|
||||
* </p>
|
||||
* <p>
|
||||
* The purpose of this exception class is analogous to the default JDK exception
|
||||
* class {@link java.util.concurrent.ExecutionException}, i.e. it wraps an
|
||||
* exception that occurred during the execution of a task. However, in contrast
|
||||
* to {@code ExecutionException}, it wraps only checked exceptions. Runtime
|
||||
* exceptions are thrown directly.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ConcurrentException extends Exception {
|
||||
/**
|
||||
* The serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 6622707671812226130L;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized instance of {@code ConcurrentException}.
|
||||
*/
|
||||
protected ConcurrentException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code ConcurrentException} and initializes it
|
||||
* with the given cause.
|
||||
*
|
||||
* @param cause the cause of this exception
|
||||
* @throws IllegalArgumentException if the cause is not a checked exception
|
||||
*/
|
||||
public ConcurrentException(final Throwable cause) {
|
||||
super(ConcurrentUtils.checkedException(cause));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code ConcurrentException} and initializes it
|
||||
* with the given message and cause.
|
||||
*
|
||||
* @param msg the error message
|
||||
* @param cause the cause of this exception
|
||||
* @throws IllegalArgumentException if the cause is not a checked exception
|
||||
*/
|
||||
public ConcurrentException(final String msg, final Throwable cause) {
|
||||
super(msg, ConcurrentUtils.checkedException(cause));
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.concurrent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Definition of an interface for the thread-safe initialization of objects.
|
||||
* </p>
|
||||
* <p>
|
||||
* The idea behind this interface is to provide access to an object in a
|
||||
* thread-safe manner. A {@code ConcurrentInitializer} can be passed to multiple
|
||||
* threads which can all access the object produced by the initializer. Through
|
||||
* the {@link #get()} method the object can be queried.
|
||||
* </p>
|
||||
* <p>
|
||||
* Concrete implementations of this interface will use different strategies for
|
||||
* the creation of the managed object, e.g. lazy initialization or
|
||||
* initialization in a background thread. This is completely transparent to
|
||||
* client code, so it is possible to change the initialization strategy without
|
||||
* affecting clients.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.0
|
||||
* @param <T> the type of the object managed by this initializer class
|
||||
*/
|
||||
public interface ConcurrentInitializer<T> {
|
||||
/**
|
||||
* Returns the fully initialized object produced by this {@code
|
||||
* ConcurrentInitializer}. A concrete implementation here returns the
|
||||
* results of the initialization process. This method may block until
|
||||
* results are available. Typically, once created the result object is
|
||||
* always the same.
|
||||
*
|
||||
* @return the object created by this {@code ConcurrentException}
|
||||
* @throws ConcurrentException if an error occurred during initialization of
|
||||
* the object
|
||||
*/
|
||||
T get() throws ConcurrentException;
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.concurrent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* An exception class used for reporting runtime error conditions related to
|
||||
* accessing data of background tasks.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class is an analogue of the {@link ConcurrentException} exception class.
|
||||
* However, it is a runtime exception and thus does not need explicit catch
|
||||
* clauses. Some methods of {@link ConcurrentUtils} throw {@code
|
||||
* ConcurrentRuntimeException} exceptions rather than
|
||||
* {@link ConcurrentException} exceptions. They can be used by client code that
|
||||
* does not want to be bothered with checked exceptions.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ConcurrentRuntimeException extends RuntimeException {
|
||||
/**
|
||||
* The serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = -6582182735562919670L;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized instance of {@code
|
||||
* ConcurrentRuntimeException}.
|
||||
*/
|
||||
protected ConcurrentRuntimeException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code ConcurrentRuntimeException} and
|
||||
* initializes it with the given cause.
|
||||
*
|
||||
* @param cause the cause of this exception
|
||||
* @throws IllegalArgumentException if the cause is not a checked exception
|
||||
*/
|
||||
public ConcurrentRuntimeException(final Throwable cause) {
|
||||
super(ConcurrentUtils.checkedException(cause));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code ConcurrentRuntimeException} and
|
||||
* initializes it with the given message and cause.
|
||||
*
|
||||
* @param msg the error message
|
||||
* @param cause the cause of this exception
|
||||
* @throws IllegalArgumentException if the cause is not a checked exception
|
||||
*/
|
||||
public ConcurrentRuntimeException(final String msg, final Throwable cause) {
|
||||
super(msg, ConcurrentUtils.checkedException(cause));
|
||||
}
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.concurrent;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* An utility class providing functionality related to the {@code
|
||||
* java.util.concurrent} package.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ConcurrentUtils {
|
||||
|
||||
/**
|
||||
* Private constructor so that no instances can be created. This class
|
||||
* contains only static utility methods.
|
||||
*/
|
||||
private ConcurrentUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the cause of the specified {@code ExecutionException} and
|
||||
* creates a {@code ConcurrentException} with the checked cause if
|
||||
* necessary. This method performs the following checks on the cause of the
|
||||
* passed in exception:
|
||||
* <ul>
|
||||
* <li>If the passed in exception is <b>null</b> or the cause is
|
||||
* <b>null</b>, this method returns <b>null</b>.</li>
|
||||
* <li>If the cause is a runtime exception, it is directly thrown.</li>
|
||||
* <li>If the cause is an error, it is directly thrown, too.</li>
|
||||
* <li>In any other case the cause is a checked exception. The method then
|
||||
* creates a {@link ConcurrentException}, initializes it with the cause, and
|
||||
* returns it.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param ex the exception to be processed
|
||||
* @return a {@code ConcurrentException} with the checked cause
|
||||
*/
|
||||
public static ConcurrentException extractCause(final ExecutionException ex) {
|
||||
if (ex == null || ex.getCause() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throwCause(ex);
|
||||
return new ConcurrentException(ex.getMessage(), ex.getCause());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the cause of the specified {@code ExecutionException} and
|
||||
* creates a {@code ConcurrentRuntimeException} with the checked cause if
|
||||
* necessary. This method works exactly like
|
||||
* {@link #extractCause(ExecutionException)}. The only difference is that
|
||||
* the cause of the specified {@code ExecutionException} is extracted as a
|
||||
* runtime exception. This is an alternative for client code that does not
|
||||
* want to deal with checked exceptions.
|
||||
*
|
||||
* @param ex the exception to be processed
|
||||
* @return a {@code ConcurrentRuntimeException} with the checked cause
|
||||
*/
|
||||
public static ConcurrentRuntimeException extractCauseUnchecked(
|
||||
final ExecutionException ex) {
|
||||
if (ex == null || ex.getCause() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throwCause(ex);
|
||||
return new ConcurrentRuntimeException(ex.getMessage(), ex.getCause());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the specified {@code ExecutionException}. This method calls
|
||||
* {@link #extractCause(ExecutionException)} for obtaining the cause of the
|
||||
* exception - which might already cause an unchecked exception or an error
|
||||
* being thrown. If the cause is a checked exception however, it is wrapped
|
||||
* in a {@code ConcurrentException}, which is thrown. If the passed in
|
||||
* exception is <b>null</b> or has no cause, the method simply returns
|
||||
* without throwing an exception.
|
||||
*
|
||||
* @param ex the exception to be handled
|
||||
* @throws ConcurrentException if the cause of the {@code
|
||||
* ExecutionException} is a checked exception
|
||||
*/
|
||||
public static void handleCause(final ExecutionException ex)
|
||||
throws ConcurrentException {
|
||||
final ConcurrentException cex = extractCause(ex);
|
||||
|
||||
if (cex != null) {
|
||||
throw cex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the specified {@code ExecutionException} and transforms it into a
|
||||
* runtime exception. This method works exactly like
|
||||
* {@link #handleCause(ExecutionException)}, but instead of a
|
||||
* {@link ConcurrentException} it throws a
|
||||
* {@link ConcurrentRuntimeException}. This is an alternative for client
|
||||
* code that does not want to deal with checked exceptions.
|
||||
*
|
||||
* @param ex the exception to be handled
|
||||
* @throws ConcurrentRuntimeException if the cause of the {@code
|
||||
* ExecutionException} is a checked exception; this exception is then
|
||||
* wrapped in the thrown runtime exception
|
||||
*/
|
||||
public static void handleCauseUnchecked(final ExecutionException ex) {
|
||||
final ConcurrentRuntimeException crex = extractCauseUnchecked(ex);
|
||||
|
||||
if (crex != null) {
|
||||
throw crex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the specified {@code Throwable} is a checked exception. If
|
||||
* not, an exception is thrown.
|
||||
*
|
||||
* @param ex the {@code Throwable} to check
|
||||
* @return a flag whether the passed in exception is a checked exception
|
||||
* @throws IllegalArgumentException if the {@code Throwable} is not a
|
||||
* checked exception
|
||||
*/
|
||||
static Throwable checkedException(final Throwable ex) {
|
||||
Validate.isTrue(ex != null && !(ex instanceof RuntimeException)
|
||||
&& !(ex instanceof Error), "Not a checked exception: " + ex);
|
||||
|
||||
return ex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the cause of the specified {@code ExecutionException}
|
||||
* should be thrown and does it if necessary.
|
||||
*
|
||||
* @param ex the exception in question
|
||||
*/
|
||||
private static void throwCause(final ExecutionException ex) {
|
||||
if (ex.getCause() instanceof RuntimeException) {
|
||||
throw (RuntimeException) ex.getCause();
|
||||
}
|
||||
|
||||
if (ex.getCause() instanceof Error) {
|
||||
throw (Error) ex.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Invokes the specified {@code ConcurrentInitializer} and returns the
|
||||
* object produced by the initializer. This method just invokes the {@code
|
||||
* get()} method of the given {@code ConcurrentInitializer}. It is
|
||||
* <b>null</b>-safe: if the argument is <b>null</b>, result is also
|
||||
* <b>null</b>.
|
||||
*
|
||||
* @param <T> the type of the object produced by the initializer
|
||||
* @param initializer the {@code ConcurrentInitializer} to be invoked
|
||||
* @return the object managed by the {@code ConcurrentInitializer}
|
||||
* @throws ConcurrentException if the {@code ConcurrentInitializer} throws
|
||||
* an exception
|
||||
*/
|
||||
public static <T> T initialize(final ConcurrentInitializer<T> initializer)
|
||||
throws ConcurrentException {
|
||||
return initializer != null ? initializer.get() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified {@code ConcurrentInitializer} and transforms
|
||||
* occurring exceptions to runtime exceptions. This method works like
|
||||
* {@link #initialize(ConcurrentInitializer)}, but if the {@code
|
||||
* ConcurrentInitializer} throws a {@link ConcurrentException}, it is
|
||||
* caught, and the cause is wrapped in a {@link ConcurrentRuntimeException}.
|
||||
* So client code does not have to deal with checked exceptions.
|
||||
*
|
||||
* @param <T> the type of the object produced by the initializer
|
||||
* @param initializer the {@code ConcurrentInitializer} to be invoked
|
||||
* @return the object managed by the {@code ConcurrentInitializer}
|
||||
* @throws ConcurrentRuntimeException if the initializer throws an exception
|
||||
*/
|
||||
public static <T> T initializeUnchecked(final ConcurrentInitializer<T> initializer) {
|
||||
try {
|
||||
return initialize(initializer);
|
||||
} catch (final ConcurrentException cex) {
|
||||
throw new ConcurrentRuntimeException(cex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>
|
||||
* Puts a value in the specified {@code ConcurrentMap} if the key is not yet
|
||||
* present. This method works similar to the {@code putIfAbsent()} method of
|
||||
* the {@code ConcurrentMap} interface, but the value returned is different.
|
||||
* Basically, this method is equivalent to the following code fragment:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* if (!map.containsKey(key)) {
|
||||
* map.put(key, value);
|
||||
* return value;
|
||||
* } else {
|
||||
* return map.get(key);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* except that the action is performed atomically. So this method always
|
||||
* returns the value which is stored in the map.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is <b>null</b>-safe: It accepts a <b>null</b> map as input
|
||||
* without throwing an exception. In this case the return value is
|
||||
* <b>null</b>, too.
|
||||
* </p>
|
||||
*
|
||||
* @param <K> the type of the keys of the map
|
||||
* @param <V> the type of the values of the map
|
||||
* @param map the map to be modified
|
||||
* @param key the key of the value to be added
|
||||
* @param value the value to be added
|
||||
* @return the value stored in the map after this operation
|
||||
*/
|
||||
public static <K, V> V putIfAbsent(final ConcurrentMap<K, V> map, final K key, final V value) {
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final V result = map.putIfAbsent(key, value);
|
||||
return result != null ? result : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a concurrent map contains a key and creates a corresponding
|
||||
* value if not. This method first checks the presence of the key in the
|
||||
* given map. If it is already contained, its value is returned. Otherwise
|
||||
* the {@code get()} method of the passed in {@link ConcurrentInitializer}
|
||||
* is called. With the resulting object
|
||||
* {@link #putIfAbsent(ConcurrentMap, Object, Object)} is called. This
|
||||
* handles the case that in the meantime another thread has added the key to
|
||||
* the map. Both the map and the initializer can be <b>null</b>; in this
|
||||
* case this method simply returns <b>null</b>.
|
||||
*
|
||||
* @param <K> the type of the keys of the map
|
||||
* @param <V> the type of the values of the map
|
||||
* @param map the map to be modified
|
||||
* @param key the key of the value to be added
|
||||
* @param init the {@link ConcurrentInitializer} for creating the value
|
||||
* @return the value stored in the map after this operation; this may or may
|
||||
* not be the object created by the {@link ConcurrentInitializer}
|
||||
* @throws ConcurrentException if the initializer throws an exception
|
||||
*/
|
||||
public static <K, V> V createIfAbsent(final ConcurrentMap<K, V> map, final K key,
|
||||
final ConcurrentInitializer<V> init) throws ConcurrentException {
|
||||
if (map == null || init == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final V value = map.get(key);
|
||||
if (value == null) {
|
||||
return putIfAbsent(map, key, init.get());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a concurrent map contains a key and creates a corresponding
|
||||
* value if not, suppressing checked exceptions. This method calls
|
||||
* {@code createIfAbsent()}. If a {@link ConcurrentException} is thrown, it
|
||||
* is caught and re-thrown as a {@link ConcurrentRuntimeException}.
|
||||
*
|
||||
* @param <K> the type of the keys of the map
|
||||
* @param <V> the type of the values of the map
|
||||
* @param map the map to be modified
|
||||
* @param key the key of the value to be added
|
||||
* @param init the {@link ConcurrentInitializer} for creating the value
|
||||
* @return the value stored in the map after this operation; this may or may
|
||||
* not be the object created by the {@link ConcurrentInitializer}
|
||||
* @throws ConcurrentRuntimeException if the initializer throws an exception
|
||||
*/
|
||||
public static <K, V> V createIfAbsentUnchecked(final ConcurrentMap<K, V> map,
|
||||
final K key, final ConcurrentInitializer<V> init) {
|
||||
try {
|
||||
return createIfAbsent(map, key, init);
|
||||
} catch (final ConcurrentException cex) {
|
||||
throw new ConcurrentRuntimeException(cex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>
|
||||
* Gets an implementation of <code>Future</code> that is immediately done
|
||||
* and returns the specified constant value.
|
||||
* </p>
|
||||
* <p>
|
||||
* This can be useful to return a simple constant immediately from the
|
||||
* concurrent processing, perhaps as part of avoiding nulls.
|
||||
* A constant future can also be useful in testing.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the value used by this {@code Future} object
|
||||
* @param value the constant value to return, may be null
|
||||
* @return an instance of Future that will return the value, never null
|
||||
*/
|
||||
public static <T> Future<T> constantFuture(final T value) {
|
||||
return new ConstantFuture<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* A specialized {@code Future} implementation which wraps a constant value.
|
||||
* @param <T> the type of the value wrapped by this class
|
||||
*/
|
||||
static final class ConstantFuture<T> implements Future<T> {
|
||||
/** The constant value. */
|
||||
private final T value;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code ConstantFuture} and initializes it
|
||||
* with the constant value.
|
||||
*
|
||||
* @param value the value (may be <b>null</b>)
|
||||
*/
|
||||
ConstantFuture(final T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc} This implementation always returns <b>true</b> because
|
||||
* the constant object managed by this {@code Future} implementation is
|
||||
* always available.
|
||||
*/
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc} This implementation just returns the constant value.
|
||||
*/
|
||||
@Override
|
||||
public T get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc} This implementation just returns the constant value; it
|
||||
* does not block, therefore the timeout has no meaning.
|
||||
*/
|
||||
@Override
|
||||
public T get(final long timeout, final TimeUnit unit) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc} This implementation always returns <b>false</b>; there
|
||||
* is no background process which could be cancelled.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc} The cancel operation is not supported. This
|
||||
* implementation always returns <b>false</b>.
|
||||
*/
|
||||
@Override
|
||||
public boolean cancel(final boolean mayInterruptIfRunning) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a clone cannot be created. In contrast to
|
||||
* {@link CloneNotSupportedException} this is a {@link RuntimeException}.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CloneFailedException extends RuntimeException {
|
||||
// ~ Static fields/initializers ---------------------------------------------
|
||||
|
||||
private static final long serialVersionUID = 20091223L;
|
||||
|
||||
// ~ Constructors -----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a CloneFailedException.
|
||||
*
|
||||
* @param message description of the exception
|
||||
* @since upcoming
|
||||
*/
|
||||
public CloneFailedException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CloneFailedException.
|
||||
*
|
||||
* @param cause cause of the exception
|
||||
* @since upcoming
|
||||
*/
|
||||
public CloneFailedException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CloneFailedException.
|
||||
*
|
||||
* @param message description of the exception
|
||||
* @param cause cause of the exception
|
||||
* @since upcoming
|
||||
*/
|
||||
public CloneFailedException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.exception;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* An exception that provides an easy and safe way to add contextual information.
|
||||
* </p><p>
|
||||
* An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
|
||||
* Frequently what is needed is a select few pieces of local contextual data.
|
||||
* Providing this data is tricky however, due to concerns over formatting and nulls.
|
||||
* </p><p>
|
||||
* The contexted exception approach allows the exception to be created together with a
|
||||
* list of context label-value pairs. This additional information is automatically included in
|
||||
* the message and printed stack trace.
|
||||
* </p><p>
|
||||
* An unchecked version of this exception is provided by ContextedRuntimeException.
|
||||
* </p>
|
||||
* <p>
|
||||
* To use this class write code as follows:
|
||||
* </p>
|
||||
* <pre>
|
||||
* try {
|
||||
* ...
|
||||
* } catch (Exception e) {
|
||||
* throw new ContextedException("Error posting account transaction", e)
|
||||
* .addContextValue("Account Number", accountNumber)
|
||||
* .addContextValue("Amount Posted", amountPosted)
|
||||
* .addContextValue("Previous Balance", previousBalance)
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* or improve diagnose data at a higher level:
|
||||
* </p>
|
||||
* <pre>
|
||||
* try {
|
||||
* ...
|
||||
* } catch (ContextedException e) {
|
||||
* throw e.setContextValue("Transaction Id", transactionId);
|
||||
* } catch (Exception e) {
|
||||
* if (e instanceof ExceptionContext) {
|
||||
* e.setContextValue("Transaction Id", transactionId);
|
||||
* }
|
||||
* throw e;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* The output in a printStacktrace() (which often is written to a log) would look something like the following:
|
||||
* </p>
|
||||
* <pre>
|
||||
* ContextedException: java.lang.Exception: Error posting account transaction
|
||||
* Exception Context:
|
||||
* [1:Account Number=null]
|
||||
* [2:Amount Posted=100.00]
|
||||
* [3:Previous Balance=-2.17]
|
||||
* [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
|
||||
*
|
||||
* ---------------------------------
|
||||
* at org.apache.commons.lang3.exception.ContextedExceptionTest.testAddValue(ContextedExceptionTest.java:88)
|
||||
* ..... (rest of trace)
|
||||
* </pre>
|
||||
*
|
||||
* @see ContextedRuntimeException
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ContextedException extends Exception implements ExceptionContext {
|
||||
|
||||
/** The serialization version. */
|
||||
private static final long serialVersionUID = 20110706L;
|
||||
/** The context where the data is stored. */
|
||||
private final ExceptionContext exceptionContext;
|
||||
|
||||
/**
|
||||
* Instantiates ContextedException without message or cause.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*/
|
||||
public ContextedException() {
|
||||
super();
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedException with message, but without cause.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*
|
||||
* @param message the exception message, may be null
|
||||
*/
|
||||
public ContextedException(final String message) {
|
||||
super(message);
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedException with cause, but without message.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*
|
||||
* @param cause the underlying cause of the exception, may be null
|
||||
*/
|
||||
public ContextedException(final Throwable cause) {
|
||||
super(cause);
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedException with cause and message.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*
|
||||
* @param message the exception message, may be null
|
||||
* @param cause the underlying cause of the exception, may be null
|
||||
*/
|
||||
public ContextedException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedException with cause, message, and ExceptionContext.
|
||||
*
|
||||
* @param message the exception message, may be null
|
||||
* @param cause the underlying cause of the exception, may be null
|
||||
* @param context the context used to store the additional information, null uses default implementation
|
||||
*/
|
||||
public ContextedException(final String message, final Throwable cause, ExceptionContext context) {
|
||||
super(message, cause);
|
||||
if (context == null) {
|
||||
context = new DefaultExceptionContext();
|
||||
}
|
||||
exceptionContext = context;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Adds information helpful to a developer in diagnosing and correcting the problem.
|
||||
* For the information to be meaningful, the value passed should have a reasonable
|
||||
* toString() implementation.
|
||||
* Different values can be added with the same label multiple times.
|
||||
* <p>
|
||||
* Note: This exception is only serializable if the object added is serializable.
|
||||
* </p>
|
||||
*
|
||||
* @param label a textual label associated with information, {@code null} not recommended
|
||||
* @param value information needed to understand exception, may be {@code null}
|
||||
* @return {@code this}, for method chaining, not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public ContextedException addContextValue(final String label, final Object value) {
|
||||
exceptionContext.addContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets information helpful to a developer in diagnosing and correcting the problem.
|
||||
* For the information to be meaningful, the value passed should have a reasonable
|
||||
* toString() implementation.
|
||||
* Any existing values with the same labels are removed before the new one is added.
|
||||
* <p>
|
||||
* Note: This exception is only serializable if the object added as value is serializable.
|
||||
* </p>
|
||||
*
|
||||
* @param label a textual label associated with information, {@code null} not recommended
|
||||
* @param value information needed to understand exception, may be {@code null}
|
||||
* @return {@code this}, for method chaining, not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public ContextedException setContextValue(final String label, final Object value) {
|
||||
exceptionContext.setContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Object> getContextValues(final String label) {
|
||||
return this.exceptionContext.getContextValues(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object getFirstContextValue(final String label) {
|
||||
return this.exceptionContext.getFirstContextValue(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Pair<String, Object>> getContextEntries() {
|
||||
return this.exceptionContext.getContextEntries();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getContextLabels() {
|
||||
return exceptionContext.getContextLabels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the message explaining the exception, including the contextual data.
|
||||
*
|
||||
* @see Throwable#getMessage()
|
||||
* @return the message, never null
|
||||
*/
|
||||
@Override
|
||||
public String getMessage(){
|
||||
return getFormattedExceptionMessage(super.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the message explaining the exception without the contextual data.
|
||||
*
|
||||
* @see Throwable#getMessage()
|
||||
* @return the message
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public String getRawMessage() {
|
||||
return super.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getFormattedExceptionMessage(final String baseMessage) {
|
||||
return exceptionContext.getFormattedExceptionMessage(baseMessage);
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.exception;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A runtime exception that provides an easy and safe way to add contextual information.
|
||||
* </p><p>
|
||||
* An exception trace itself is often insufficient to provide rapid diagnosis of the issue.
|
||||
* Frequently what is needed is a select few pieces of local contextual data.
|
||||
* Providing this data is tricky however, due to concerns over formatting and nulls.
|
||||
* </p><p>
|
||||
* The contexted exception approach allows the exception to be created together with a
|
||||
* list of context label-value pairs. This additional information is automatically included in
|
||||
* the message and printed stack trace.
|
||||
* </p><p>
|
||||
* A checked version of this exception is provided by ContextedException.
|
||||
* </p>
|
||||
* <p>
|
||||
* To use this class write code as follows:
|
||||
* </p>
|
||||
* <pre>
|
||||
* try {
|
||||
* ...
|
||||
* } catch (Exception e) {
|
||||
* throw new ContextedRuntimeException("Error posting account transaction", e)
|
||||
* .addContextValue("Account Number", accountNumber)
|
||||
* .addContextValue("Amount Posted", amountPosted)
|
||||
* .addContextValue("Previous Balance", previousBalance)
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* or improve diagnose data at a higher level:
|
||||
* </p>
|
||||
* <pre>
|
||||
* try {
|
||||
* ...
|
||||
* } catch (ContextedRuntimeException e) {
|
||||
* throw e.setContextValue("Transaction Id", transactionId);
|
||||
* } catch (Exception e) {
|
||||
* if (e instanceof ExceptionContext) {
|
||||
* e.setContextValue("Transaction Id", transactionId);
|
||||
* }
|
||||
* throw e;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* The output in a printStacktrace() (which often is written to a log) would look something like the following:
|
||||
* </p>
|
||||
* <pre>
|
||||
* ContextedRuntimeException: java.lang.Exception: Error posting account transaction
|
||||
* Exception Context:
|
||||
* [1:Account Number=null]
|
||||
* [2:Amount Posted=100.00]
|
||||
* [3:Previous Balance=-2.17]
|
||||
* [4:Transaction Id=94ef1d15-d443-46c4-822b-637f26244899]
|
||||
*
|
||||
* ---------------------------------
|
||||
* at org.apache.commons.lang3.exception.ContextedRuntimeExceptionTest.testAddValue(ContextedExceptionTest.java:88)
|
||||
* ..... (rest of trace)
|
||||
* </pre>
|
||||
*
|
||||
* @see ContextedException
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ContextedRuntimeException extends RuntimeException implements ExceptionContext {
|
||||
|
||||
/** The serialization version. */
|
||||
private static final long serialVersionUID = 20110706L;
|
||||
/** The context where the data is stored. */
|
||||
private final ExceptionContext exceptionContext;
|
||||
|
||||
/**
|
||||
* Instantiates ContextedRuntimeException without message or cause.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*/
|
||||
public ContextedRuntimeException() {
|
||||
super();
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedRuntimeException with message, but without cause.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*
|
||||
* @param message the exception message, may be null
|
||||
*/
|
||||
public ContextedRuntimeException(final String message) {
|
||||
super(message);
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedRuntimeException with cause, but without message.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*
|
||||
* @param cause the underlying cause of the exception, may be null
|
||||
*/
|
||||
public ContextedRuntimeException(final Throwable cause) {
|
||||
super(cause);
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedRuntimeException with cause and message.
|
||||
* <p>
|
||||
* The context information is stored using a default implementation.
|
||||
*
|
||||
* @param message the exception message, may be null
|
||||
* @param cause the underlying cause of the exception, may be null
|
||||
*/
|
||||
public ContextedRuntimeException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
exceptionContext = new DefaultExceptionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates ContextedRuntimeException with cause, message, and ExceptionContext.
|
||||
*
|
||||
* @param message the exception message, may be null
|
||||
* @param cause the underlying cause of the exception, may be null
|
||||
* @param context the context used to store the additional information, null uses default implementation
|
||||
*/
|
||||
public ContextedRuntimeException(final String message, final Throwable cause, ExceptionContext context) {
|
||||
super(message, cause);
|
||||
if (context == null) {
|
||||
context = new DefaultExceptionContext();
|
||||
}
|
||||
exceptionContext = context;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Adds information helpful to a developer in diagnosing and correcting the problem.
|
||||
* For the information to be meaningful, the value passed should have a reasonable
|
||||
* toString() implementation.
|
||||
* Different values can be added with the same label multiple times.
|
||||
* <p>
|
||||
* Note: This exception is only serializable if the object added is serializable.
|
||||
* </p>
|
||||
*
|
||||
* @param label a textual label associated with information, {@code null} not recommended
|
||||
* @param value information needed to understand exception, may be {@code null}
|
||||
* @return {@code this}, for method chaining, not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public ContextedRuntimeException addContextValue(final String label, final Object value) {
|
||||
exceptionContext.addContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets information helpful to a developer in diagnosing and correcting the problem.
|
||||
* For the information to be meaningful, the value passed should have a reasonable
|
||||
* toString() implementation.
|
||||
* Any existing values with the same labels are removed before the new one is added.
|
||||
* <p>
|
||||
* Note: This exception is only serializable if the object added as value is serializable.
|
||||
* </p>
|
||||
*
|
||||
* @param label a textual label associated with information, {@code null} not recommended
|
||||
* @param value information needed to understand exception, may be {@code null}
|
||||
* @return {@code this}, for method chaining, not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public ContextedRuntimeException setContextValue(final String label, final Object value) {
|
||||
exceptionContext.setContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Object> getContextValues(final String label) {
|
||||
return this.exceptionContext.getContextValues(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object getFirstContextValue(final String label) {
|
||||
return this.exceptionContext.getFirstContextValue(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Pair<String, Object>> getContextEntries() {
|
||||
return this.exceptionContext.getContextEntries();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getContextLabels() {
|
||||
return exceptionContext.getContextLabels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the message explaining the exception, including the contextual data.
|
||||
*
|
||||
* @see Throwable#getMessage()
|
||||
* @return the message, never null
|
||||
*/
|
||||
@Override
|
||||
public String getMessage(){
|
||||
return getFormattedExceptionMessage(super.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the message explaining the exception without the contextual data.
|
||||
*
|
||||
* @see Throwable#getMessage()
|
||||
* @return the message
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public String getRawMessage() {
|
||||
return super.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getFormattedExceptionMessage(final String baseMessage) {
|
||||
return exceptionContext.getFormattedExceptionMessage(baseMessage);
|
||||
}
|
||||
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.exception;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.StringUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.tuple.ImmutablePair;
|
||||
import com.qihoo360.replugin.ext.lang3.tuple.Pair;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default implementation of the context storing the label-value pairs for contexted exceptions.
|
||||
* <p>
|
||||
* This implementation is serializable, however this is dependent on the values that
|
||||
* are added also being serializable.
|
||||
* </p>
|
||||
*
|
||||
* @see ContextedException
|
||||
* @see ContextedRuntimeException
|
||||
* @since 3.0
|
||||
*/
|
||||
public class DefaultExceptionContext implements ExceptionContext, Serializable {
|
||||
|
||||
/** The serialization version. */
|
||||
private static final long serialVersionUID = 20110706L;
|
||||
|
||||
/** The list storing the label-data pairs. */
|
||||
private final List<Pair<String, Object>> contextValues = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public DefaultExceptionContext addContextValue(final String label, final Object value) {
|
||||
contextValues.add(new ImmutablePair<>(label, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public DefaultExceptionContext setContextValue(final String label, final Object value) {
|
||||
for (final Iterator<Pair<String, Object>> iter = contextValues.iterator(); iter.hasNext();) {
|
||||
final Pair<String, Object> p = iter.next();
|
||||
if (StringUtils.equals(label, p.getKey())) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
addContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Object> getContextValues(final String label) {
|
||||
final List<Object> values = new ArrayList<>();
|
||||
for (final Pair<String, Object> pair : contextValues) {
|
||||
if (StringUtils.equals(label, pair.getKey())) {
|
||||
values.add(pair.getValue());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object getFirstContextValue(final String label) {
|
||||
for (final Pair<String, Object> pair : contextValues) {
|
||||
if (StringUtils.equals(label, pair.getKey())) {
|
||||
return pair.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getContextLabels() {
|
||||
final Set<String> labels = new HashSet<>();
|
||||
for (final Pair<String, Object> pair : contextValues) {
|
||||
labels.add(pair.getKey());
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Pair<String, Object>> getContextEntries() {
|
||||
return contextValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the message containing the contextual information.
|
||||
*
|
||||
* @param baseMessage the base exception message <b>without</b> context information appended
|
||||
* @return the exception message <b>with</b> context information appended, never null
|
||||
*/
|
||||
@Override
|
||||
public String getFormattedExceptionMessage(final String baseMessage){
|
||||
final StringBuilder buffer = new StringBuilder(256);
|
||||
if (baseMessage != null) {
|
||||
buffer.append(baseMessage);
|
||||
}
|
||||
|
||||
if (contextValues.size() > 0) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append('\n');
|
||||
}
|
||||
buffer.append("Exception Context:\n");
|
||||
|
||||
int i = 0;
|
||||
for (final Pair<String, Object> pair : contextValues) {
|
||||
buffer.append("\t[");
|
||||
buffer.append(++i);
|
||||
buffer.append(':');
|
||||
buffer.append(pair.getKey());
|
||||
buffer.append("=");
|
||||
final Object value = pair.getValue();
|
||||
if (value == null) {
|
||||
buffer.append("null");
|
||||
} else {
|
||||
String valueStr;
|
||||
try {
|
||||
valueStr = value.toString();
|
||||
} catch (final Exception e) {
|
||||
valueStr = "Exception thrown on toString(): " + ExceptionUtils.getStackTrace(e);
|
||||
}
|
||||
buffer.append(valueStr);
|
||||
}
|
||||
buffer.append("]\n");
|
||||
}
|
||||
buffer.append("---------------------------------");
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.exception;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.tuple.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Allows the storage and retrieval of contextual information based on label-value
|
||||
* pairs for exceptions.
|
||||
* <p>
|
||||
* Implementations are expected to manage the pairs in a list-style collection
|
||||
* that keeps the pairs in the sequence of their addition.
|
||||
* </p>
|
||||
*
|
||||
* @see ContextedException
|
||||
* @see ContextedRuntimeException
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface ExceptionContext {
|
||||
|
||||
/**
|
||||
* Adds a contextual label-value pair into this context.
|
||||
* <p>
|
||||
* The pair will be added to the context, independently of an already
|
||||
* existing pair with the same label.
|
||||
* </p>
|
||||
*
|
||||
* @param label the label of the item to add, {@code null} not recommended
|
||||
* @param value the value of item to add, may be {@code null}
|
||||
* @return {@code this}, for method chaining, not {@code null}
|
||||
*/
|
||||
ExceptionContext addContextValue(String label, Object value);
|
||||
|
||||
/**
|
||||
* Sets a contextual label-value pair into this context.
|
||||
* <p>
|
||||
* The pair will be added normally, but any existing label-value pair with
|
||||
* the same label is removed from the context.
|
||||
* </p>
|
||||
*
|
||||
* @param label the label of the item to add, {@code null} not recommended
|
||||
* @param value the value of item to add, may be {@code null}
|
||||
* @return {@code this}, for method chaining, not {@code null}
|
||||
*/
|
||||
ExceptionContext setContextValue(String label, Object value);
|
||||
|
||||
/**
|
||||
* Retrieves all the contextual data values associated with the label.
|
||||
*
|
||||
* @param label the label to get the contextual values for, may be {@code null}
|
||||
* @return the contextual values associated with the label, never {@code null}
|
||||
*/
|
||||
List<Object> getContextValues(String label);
|
||||
|
||||
/**
|
||||
* Retrieves the first available contextual data value associated with the label.
|
||||
*
|
||||
* @param label the label to get the contextual value for, may be {@code null}
|
||||
* @return the first contextual value associated with the label, may be {@code null}
|
||||
*/
|
||||
Object getFirstContextValue(String label);
|
||||
|
||||
/**
|
||||
* Retrieves the full set of labels defined in the contextual data.
|
||||
*
|
||||
* @return the set of labels, not {@code null}
|
||||
*/
|
||||
Set<String> getContextLabels();
|
||||
|
||||
/**
|
||||
* Retrieves the full list of label-value pairs defined in the contextual data.
|
||||
*
|
||||
* @return the list of pairs, not {@code null}
|
||||
*/
|
||||
List<Pair<String, Object>> getContextEntries();
|
||||
|
||||
/**
|
||||
* Gets the contextualized error message based on a base message.
|
||||
* This will add the context label-value pairs to the message.
|
||||
*
|
||||
* @param baseMessage the base exception message <b>without</b> context information appended
|
||||
* @return the exception message <b>with</b> context information appended, not {@code null}
|
||||
*/
|
||||
String getFormattedExceptionMessage(String baseMessage);
|
||||
|
||||
}
|
@ -1,824 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.exception;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ArrayUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.StringUtils;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>Provides utilities for manipulating and examining
|
||||
* <code>Throwable</code> objects.</p>
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public class ExceptionUtils {
|
||||
|
||||
/**
|
||||
* <p>Used when printing stack frames to denote the start of a
|
||||
* wrapped exception.</p>
|
||||
*
|
||||
* <p>Package private for accessibility by test suite.</p>
|
||||
*/
|
||||
static final String WRAPPED_MARKER = " [wrapped] ";
|
||||
|
||||
/**
|
||||
* <p>The names of methods commonly used to access a wrapped exception.</p>
|
||||
*/
|
||||
// TODO: Remove in Lang 4.0
|
||||
private static final String[] CAUSE_METHOD_NAMES = {
|
||||
"getCause",
|
||||
"getNextException",
|
||||
"getTargetException",
|
||||
"getException",
|
||||
"getSourceException",
|
||||
"getRootCause",
|
||||
"getCausedByException",
|
||||
"getNested",
|
||||
"getLinkedException",
|
||||
"getNestedException",
|
||||
"getLinkedCause",
|
||||
"getThrowable",
|
||||
};
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
|
||||
* normally necessary.
|
||||
* </p>
|
||||
*/
|
||||
public ExceptionUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns the default names used when searching for the cause of an exception.</p>
|
||||
*
|
||||
* <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
|
||||
*
|
||||
* @return cloned array of the default method names
|
||||
* @since 3.0
|
||||
* @deprecated This feature will be removed in Lang 4.0
|
||||
*/
|
||||
@Deprecated
|
||||
public static String[] getDefaultCauseMethodNames() {
|
||||
return ArrayUtils.clone(CAUSE_METHOD_NAMES);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
|
||||
*
|
||||
* <p>The method searches for methods with specific names that return a
|
||||
* <code>Throwable</code> object. This will pick up most wrapping exceptions,
|
||||
* including those from JDK 1.4.
|
||||
*
|
||||
* <p>The default list searched for are:</p>
|
||||
* <ul>
|
||||
* <li><code>getCause()</code></li>
|
||||
* <li><code>getNextException()</code></li>
|
||||
* <li><code>getTargetException()</code></li>
|
||||
* <li><code>getException()</code></li>
|
||||
* <li><code>getSourceException()</code></li>
|
||||
* <li><code>getRootCause()</code></li>
|
||||
* <li><code>getCausedByException()</code></li>
|
||||
* <li><code>getNested()</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If none of the above is found, returns <code>null</code>.</p>
|
||||
*
|
||||
* @param throwable the throwable to introspect for a cause, may be null
|
||||
* @return the cause of the <code>Throwable</code>,
|
||||
* <code>null</code> if none found or null throwable input
|
||||
* @since 1.0
|
||||
* @deprecated This feature will be removed in Lang 4.0, use {@link Throwable#getCause} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Throwable getCause(final Throwable throwable) {
|
||||
return getCause(throwable, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
|
||||
*
|
||||
* <p>A <code>null</code> set of method names means use the default set.
|
||||
* A <code>null</code> in the set of method names will be ignored.</p>
|
||||
*
|
||||
* @param throwable the throwable to introspect for a cause, may be null
|
||||
* @param methodNames the method names, null treated as default set
|
||||
* @return the cause of the <code>Throwable</code>,
|
||||
* <code>null</code> if none found or null throwable input
|
||||
* @since 1.0
|
||||
* @deprecated This feature will be removed in Lang 4.0, use {@link Throwable#getCause} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Throwable getCause(final Throwable throwable, String[] methodNames) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (methodNames == null) {
|
||||
final Throwable cause = throwable.getCause();
|
||||
if (cause != null) {
|
||||
return cause;
|
||||
}
|
||||
|
||||
methodNames = CAUSE_METHOD_NAMES;
|
||||
}
|
||||
|
||||
for (final String methodName : methodNames) {
|
||||
if (methodName != null) {
|
||||
final Throwable legacyCause = getCauseUsingMethodName(throwable, methodName);
|
||||
if (legacyCause != null) {
|
||||
return legacyCause;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
|
||||
*
|
||||
* <p>This method walks through the exception chain to the last element,
|
||||
* "root" of the tree, using {@link #getCause(Throwable)}, and
|
||||
* returns that exception.</p>
|
||||
*
|
||||
* <p>From version 2.2, this method handles recursive cause structures
|
||||
* that might otherwise cause infinite loops. If the throwable parameter
|
||||
* has a cause of itself, then null will be returned. If the throwable
|
||||
* parameter cause chain loops, the last element in the chain before the
|
||||
* loop is returned.</p>
|
||||
*
|
||||
* @param throwable the throwable to get the root cause for, may be null
|
||||
* @return the root cause of the <code>Throwable</code>,
|
||||
* <code>null</code> if none found or null throwable input
|
||||
*/
|
||||
public static Throwable getRootCause(final Throwable throwable) {
|
||||
final List<Throwable> list = getThrowableList(throwable);
|
||||
return list.size() < 2 ? null : list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds a <code>Throwable</code> by method name.</p>
|
||||
*
|
||||
* @param throwable the exception to examine
|
||||
* @param methodName the name of the method to find and invoke
|
||||
* @return the wrapped exception, or <code>null</code> if not found
|
||||
*/
|
||||
// TODO: Remove in Lang 4.0
|
||||
private static Throwable getCauseUsingMethodName(final Throwable throwable, final String methodName) {
|
||||
Method method = null;
|
||||
try {
|
||||
method = throwable.getClass().getMethod(methodName);
|
||||
} catch (final NoSuchMethodException | SecurityException ignored) { // NOPMD
|
||||
// exception ignored
|
||||
}
|
||||
|
||||
if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
|
||||
try {
|
||||
return (Throwable) method.invoke(throwable);
|
||||
} catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { // NOPMD
|
||||
// exception ignored
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Counts the number of <code>Throwable</code> objects in the
|
||||
* exception chain.</p>
|
||||
*
|
||||
* <p>A throwable without cause will return <code>1</code>.
|
||||
* A throwable with one cause will return <code>2</code> and so on.
|
||||
* A <code>null</code> throwable will return <code>0</code>.</p>
|
||||
*
|
||||
* <p>From version 2.2, this method handles recursive cause structures
|
||||
* that might otherwise cause infinite loops. The cause chain is
|
||||
* processed until the end is reached, or until the next item in the
|
||||
* chain is already in the result set.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @return the count of throwables, zero if null input
|
||||
*/
|
||||
public static int getThrowableCount(final Throwable throwable) {
|
||||
return getThrowableList(throwable).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the list of <code>Throwable</code> objects in the
|
||||
* exception chain.</p>
|
||||
*
|
||||
* <p>A throwable without cause will return an array containing
|
||||
* one element - the input throwable.
|
||||
* A throwable with one cause will return an array containing
|
||||
* two elements. - the input throwable and the cause throwable.
|
||||
* A <code>null</code> throwable will return an array of size zero.</p>
|
||||
*
|
||||
* <p>From version 2.2, this method handles recursive cause structures
|
||||
* that might otherwise cause infinite loops. The cause chain is
|
||||
* processed until the end is reached, or until the next item in the
|
||||
* chain is already in the result set.</p>
|
||||
*
|
||||
* @see #getThrowableList(Throwable)
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @return the array of throwables, never null
|
||||
*/
|
||||
public static Throwable[] getThrowables(final Throwable throwable) {
|
||||
final List<Throwable> list = getThrowableList(throwable);
|
||||
return list.toArray(new Throwable[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the list of <code>Throwable</code> objects in the
|
||||
* exception chain.</p>
|
||||
*
|
||||
* <p>A throwable without cause will return a list containing
|
||||
* one element - the input throwable.
|
||||
* A throwable with one cause will return a list containing
|
||||
* two elements. - the input throwable and the cause throwable.
|
||||
* A <code>null</code> throwable will return a list of size zero.</p>
|
||||
*
|
||||
* <p>This method handles recursive cause structures that might
|
||||
* otherwise cause infinite loops. The cause chain is processed until
|
||||
* the end is reached, or until the next item in the chain is already
|
||||
* in the result set.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @return the list of throwables, never null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public static List<Throwable> getThrowableList(Throwable throwable) {
|
||||
final List<Throwable> list = new ArrayList<>();
|
||||
while (throwable != null && list.contains(throwable) == false) {
|
||||
list.add(throwable);
|
||||
throwable = ExceptionUtils.getCause(throwable);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns the (zero based) index of the first <code>Throwable</code>
|
||||
* that matches the specified class (exactly) in the exception chain.
|
||||
* Subclasses of the specified class do not match - see
|
||||
* {@link #indexOfType(Throwable, Class)} for the opposite.</p>
|
||||
*
|
||||
* <p>A <code>null</code> throwable returns <code>-1</code>.
|
||||
* A <code>null</code> type returns <code>-1</code>.
|
||||
* No match in the chain returns <code>-1</code>.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @param clazz the class to search for, subclasses do not match, null returns -1
|
||||
* @return the index into the throwable chain, -1 if no match or null input
|
||||
*/
|
||||
public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz) {
|
||||
return indexOf(throwable, clazz, 0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the (zero based) index of the first <code>Throwable</code>
|
||||
* that matches the specified type in the exception chain from
|
||||
* a specified index.
|
||||
* Subclasses of the specified class do not match - see
|
||||
* {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
|
||||
*
|
||||
* <p>A <code>null</code> throwable returns <code>-1</code>.
|
||||
* A <code>null</code> type returns <code>-1</code>.
|
||||
* No match in the chain returns <code>-1</code>.
|
||||
* A negative start index is treated as zero.
|
||||
* A start index greater than the number of throwables returns <code>-1</code>.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @param clazz the class to search for, subclasses do not match, null returns -1
|
||||
* @param fromIndex the (zero based) index of the starting position,
|
||||
* negative treated as zero, larger than chain size returns -1
|
||||
* @return the index into the throwable chain, -1 if no match or null input
|
||||
*/
|
||||
public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz, final int fromIndex) {
|
||||
return indexOf(throwable, clazz, fromIndex, false);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns the (zero based) index of the first <code>Throwable</code>
|
||||
* that matches the specified class or subclass in the exception chain.
|
||||
* Subclasses of the specified class do match - see
|
||||
* {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
|
||||
*
|
||||
* <p>A <code>null</code> throwable returns <code>-1</code>.
|
||||
* A <code>null</code> type returns <code>-1</code>.
|
||||
* No match in the chain returns <code>-1</code>.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @param type the type to search for, subclasses match, null returns -1
|
||||
* @return the index into the throwable chain, -1 if no match or null input
|
||||
* @since 2.1
|
||||
*/
|
||||
public static int indexOfType(final Throwable throwable, final Class<?> type) {
|
||||
return indexOf(throwable, type, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the (zero based) index of the first <code>Throwable</code>
|
||||
* that matches the specified type in the exception chain from
|
||||
* a specified index.
|
||||
* Subclasses of the specified class do match - see
|
||||
* {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
|
||||
*
|
||||
* <p>A <code>null</code> throwable returns <code>-1</code>.
|
||||
* A <code>null</code> type returns <code>-1</code>.
|
||||
* No match in the chain returns <code>-1</code>.
|
||||
* A negative start index is treated as zero.
|
||||
* A start index greater than the number of throwables returns <code>-1</code>.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @param type the type to search for, subclasses match, null returns -1
|
||||
* @param fromIndex the (zero based) index of the starting position,
|
||||
* negative treated as zero, larger than chain size returns -1
|
||||
* @return the index into the throwable chain, -1 if no match or null input
|
||||
* @since 2.1
|
||||
*/
|
||||
public static int indexOfType(final Throwable throwable, final Class<?> type, final int fromIndex) {
|
||||
return indexOf(throwable, type, fromIndex, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Worker method for the <code>indexOfType</code> methods.</p>
|
||||
*
|
||||
* @param throwable the throwable to inspect, may be null
|
||||
* @param type the type to search for, subclasses match, null returns -1
|
||||
* @param fromIndex the (zero based) index of the starting position,
|
||||
* negative treated as zero, larger than chain size returns -1
|
||||
* @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
|
||||
* using references
|
||||
* @return index of the <code>type</code> within throwables nested within the specified <code>throwable</code>
|
||||
*/
|
||||
private static int indexOf(final Throwable throwable, final Class<?> type, int fromIndex, final boolean subclass) {
|
||||
if (throwable == null || type == null) {
|
||||
return -1;
|
||||
}
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
final Throwable[] throwables = ExceptionUtils.getThrowables(throwable);
|
||||
if (fromIndex >= throwables.length) {
|
||||
return -1;
|
||||
}
|
||||
if (subclass) {
|
||||
for (int i = fromIndex; i < throwables.length; i++) {
|
||||
if (type.isAssignableFrom(throwables[i].getClass())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = fromIndex; i < throwables.length; i++) {
|
||||
if (type.equals(throwables[i].getClass())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Prints a compact stack trace for the root cause of a throwable
|
||||
* to <code>System.err</code>.</p>
|
||||
*
|
||||
* <p>The compact stack trace starts with the root cause and prints
|
||||
* stack frames up to the place where it was caught and wrapped.
|
||||
* Then it prints the wrapped exception and continues with stack frames
|
||||
* until the wrapper exception is caught and wrapped again, etc.</p>
|
||||
*
|
||||
* <p>The output of this method is consistent across JDK versions.
|
||||
* Note that this is the opposite order to the JDK1.4 display.</p>
|
||||
*
|
||||
* <p>The method is equivalent to <code>printStackTrace</code> for throwables
|
||||
* that don't have nested causes.</p>
|
||||
*
|
||||
* @param throwable the throwable to output
|
||||
* @since 2.0
|
||||
*/
|
||||
public static void printRootCauseStackTrace(final Throwable throwable) {
|
||||
printRootCauseStackTrace(throwable, System.err);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Prints a compact stack trace for the root cause of a throwable.</p>
|
||||
*
|
||||
* <p>The compact stack trace starts with the root cause and prints
|
||||
* stack frames up to the place where it was caught and wrapped.
|
||||
* Then it prints the wrapped exception and continues with stack frames
|
||||
* until the wrapper exception is caught and wrapped again, etc.</p>
|
||||
*
|
||||
* <p>The output of this method is consistent across JDK versions.
|
||||
* Note that this is the opposite order to the JDK1.4 display.</p>
|
||||
*
|
||||
* <p>The method is equivalent to <code>printStackTrace</code> for throwables
|
||||
* that don't have nested causes.</p>
|
||||
*
|
||||
* @param throwable the throwable to output, may be null
|
||||
* @param stream the stream to output to, may not be null
|
||||
* @throws IllegalArgumentException if the stream is <code>null</code>
|
||||
* @since 2.0
|
||||
*/
|
||||
public static void printRootCauseStackTrace(final Throwable throwable, final PrintStream stream) {
|
||||
if (throwable == null) {
|
||||
return;
|
||||
}
|
||||
Validate.isTrue(stream != null, "The PrintStream must not be null");
|
||||
final String trace[] = getRootCauseStackTrace(throwable);
|
||||
for (final String element : trace) {
|
||||
stream.println(element);
|
||||
}
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Prints a compact stack trace for the root cause of a throwable.</p>
|
||||
*
|
||||
* <p>The compact stack trace starts with the root cause and prints
|
||||
* stack frames up to the place where it was caught and wrapped.
|
||||
* Then it prints the wrapped exception and continues with stack frames
|
||||
* until the wrapper exception is caught and wrapped again, etc.</p>
|
||||
*
|
||||
* <p>The output of this method is consistent across JDK versions.
|
||||
* Note that this is the opposite order to the JDK1.4 display.</p>
|
||||
*
|
||||
* <p>The method is equivalent to <code>printStackTrace</code> for throwables
|
||||
* that don't have nested causes.</p>
|
||||
*
|
||||
* @param throwable the throwable to output, may be null
|
||||
* @param writer the writer to output to, may not be null
|
||||
* @throws IllegalArgumentException if the writer is <code>null</code>
|
||||
* @since 2.0
|
||||
*/
|
||||
public static void printRootCauseStackTrace(final Throwable throwable, final PrintWriter writer) {
|
||||
if (throwable == null) {
|
||||
return;
|
||||
}
|
||||
Validate.isTrue(writer != null, "The PrintWriter must not be null");
|
||||
final String trace[] = getRootCauseStackTrace(throwable);
|
||||
for (final String element : trace) {
|
||||
writer.println(element);
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Creates a compact stack trace for the root cause of the supplied
|
||||
* <code>Throwable</code>.</p>
|
||||
*
|
||||
* <p>The output of this method is consistent across JDK versions.
|
||||
* It consists of the root exception followed by each of its wrapping
|
||||
* exceptions separated by '[wrapped]'. Note that this is the opposite
|
||||
* order to the JDK1.4 display.</p>
|
||||
*
|
||||
* @param throwable the throwable to examine, may be null
|
||||
* @return an array of stack trace frames, never null
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String[] getRootCauseStackTrace(final Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
return ArrayUtils.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
final Throwable throwables[] = getThrowables(throwable);
|
||||
final int count = throwables.length;
|
||||
final List<String> frames = new ArrayList<>();
|
||||
List<String> nextTrace = getStackFrameList(throwables[count - 1]);
|
||||
for (int i = count; --i >= 0;) {
|
||||
final List<String> trace = nextTrace;
|
||||
if (i != 0) {
|
||||
nextTrace = getStackFrameList(throwables[i - 1]);
|
||||
removeCommonFrames(trace, nextTrace);
|
||||
}
|
||||
if (i == count - 1) {
|
||||
frames.add(throwables[i].toString());
|
||||
} else {
|
||||
frames.add(WRAPPED_MARKER + throwables[i].toString());
|
||||
}
|
||||
for (int j = 0; j < trace.size(); j++) {
|
||||
frames.add(trace.get(j));
|
||||
}
|
||||
}
|
||||
return frames.toArray(new String[frames.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Removes common frames from the cause trace given the two stack traces.</p>
|
||||
*
|
||||
* @param causeFrames stack trace of a cause throwable
|
||||
* @param wrapperFrames stack trace of a wrapper throwable
|
||||
* @throws IllegalArgumentException if either argument is null
|
||||
* @since 2.0
|
||||
*/
|
||||
public static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {
|
||||
if (causeFrames == null || wrapperFrames == null) {
|
||||
throw new IllegalArgumentException("The List must not be null");
|
||||
}
|
||||
int causeFrameIndex = causeFrames.size() - 1;
|
||||
int wrapperFrameIndex = wrapperFrames.size() - 1;
|
||||
while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
|
||||
// Remove the frame from the cause trace if it is the same
|
||||
// as in the wrapper trace
|
||||
final String causeFrame = causeFrames.get(causeFrameIndex);
|
||||
final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
|
||||
if (causeFrame.equals(wrapperFrame)) {
|
||||
causeFrames.remove(causeFrameIndex);
|
||||
}
|
||||
causeFrameIndex--;
|
||||
wrapperFrameIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the stack trace from a Throwable as a String.</p>
|
||||
*
|
||||
* <p>The result of this method vary by JDK version as this method
|
||||
* uses {@link Throwable#printStackTrace(PrintWriter)}.
|
||||
* On JDK1.3 and earlier, the cause exception will not be shown
|
||||
* unless the specified throwable alters printStackTrace.</p>
|
||||
*
|
||||
* @param throwable the <code>Throwable</code> to be examined
|
||||
* @return the stack trace as generated by the exception's
|
||||
* <code>printStackTrace(PrintWriter)</code> method
|
||||
*/
|
||||
public static String getStackTrace(final Throwable throwable) {
|
||||
final StringWriter sw = new StringWriter();
|
||||
final PrintWriter pw = new PrintWriter(sw, true);
|
||||
throwable.printStackTrace(pw);
|
||||
return sw.getBuffer().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Captures the stack trace associated with the specified
|
||||
* <code>Throwable</code> object, decomposing it into a list of
|
||||
* stack frames.</p>
|
||||
*
|
||||
* <p>The result of this method vary by JDK version as this method
|
||||
* uses {@link Throwable#printStackTrace(PrintWriter)}.
|
||||
* On JDK1.3 and earlier, the cause exception will not be shown
|
||||
* unless the specified throwable alters printStackTrace.</p>
|
||||
*
|
||||
* @param throwable the <code>Throwable</code> to examine, may be null
|
||||
* @return an array of strings describing each stack frame, never null
|
||||
*/
|
||||
public static String[] getStackFrames(final Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
return ArrayUtils.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
return getStackFrames(getStackTrace(throwable));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns an array where each element is a line from the argument.</p>
|
||||
*
|
||||
* <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
|
||||
*
|
||||
* @param stackTrace a stack trace String
|
||||
* @return an array where each element is a line from the argument
|
||||
*/
|
||||
static String[] getStackFrames(final String stackTrace) {
|
||||
final String linebreak = System.lineSeparator();
|
||||
final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
|
||||
final List<String> list = new ArrayList<>();
|
||||
while (frames.hasMoreTokens()) {
|
||||
list.add(frames.nextToken());
|
||||
}
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Produces a <code>List</code> of stack frames - the message
|
||||
* is not included. Only the trace of the specified exception is
|
||||
* returned, any caused by trace is stripped.</p>
|
||||
*
|
||||
* <p>This works in most cases - it will only fail if the exception
|
||||
* message contains a line that starts with:
|
||||
* <code>" at".</code></p>
|
||||
*
|
||||
* @param t is any throwable
|
||||
* @return List of stack frames
|
||||
*/
|
||||
static List<String> getStackFrameList(final Throwable t) {
|
||||
final String stackTrace = getStackTrace(t);
|
||||
final String linebreak = System.lineSeparator();
|
||||
final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
|
||||
final List<String> list = new ArrayList<>();
|
||||
boolean traceStarted = false;
|
||||
while (frames.hasMoreTokens()) {
|
||||
final String token = frames.nextToken();
|
||||
// Determine if the line starts with <whitespace>at
|
||||
final int at = token.indexOf("at");
|
||||
if (at != -1 && token.substring(0, at).trim().isEmpty()) {
|
||||
traceStarted = true;
|
||||
list.add(token);
|
||||
} else if (traceStarted) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets a short message summarising the exception.
|
||||
* <p>
|
||||
* The message returned is of the form
|
||||
* {ClassNameWithoutPackage}: {ThrowableMessage}
|
||||
*
|
||||
* @param th the throwable to get a message for, null returns empty string
|
||||
* @return the message, non-null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public static String getMessage(final Throwable th) {
|
||||
if (th == null) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
final String clsName = ClassUtils.getShortClassName(th, null);
|
||||
final String msg = th.getMessage();
|
||||
return clsName + ": " + StringUtils.defaultString(msg);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets a short message summarising the root cause exception.
|
||||
* <p>
|
||||
* The message returned is of the form
|
||||
* {ClassNameWithoutPackage}: {ThrowableMessage}
|
||||
*
|
||||
* @param th the throwable to get a message for, null returns empty string
|
||||
* @return the message, non-null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public static String getRootCauseMessage(final Throwable th) {
|
||||
Throwable root = ExceptionUtils.getRootCause(th);
|
||||
root = root == null ? th : root;
|
||||
return getMessage(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a checked exception without adding the exception to the throws
|
||||
* clause of the calling method. This method prevents throws clause
|
||||
* pollution and reduces the clutter of "Caused by" exceptions in the
|
||||
* stacktrace.
|
||||
* <p>
|
||||
* The use of this technique may be controversial, but exceedingly useful to
|
||||
* library developers.
|
||||
* <code>
|
||||
* public int propagateExample { // note that there is no throws clause
|
||||
* try {
|
||||
* return invocation(); // throws IOException
|
||||
* } catch (Exception e) {
|
||||
* return ExceptionUtils.rethrow(e); // propagates a checked exception
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* <p>
|
||||
* This is an alternative to the more conservative approach of wrapping the
|
||||
* checked exception in a RuntimeException:
|
||||
* <code>
|
||||
* public int wrapExample { // note that there is no throws clause
|
||||
* try {
|
||||
* return invocation(); // throws IOException
|
||||
* } catch (Error e) {
|
||||
* throw e;
|
||||
* } catch (RuntimeException e) {
|
||||
* throw e; // wraps a checked exception
|
||||
* } catch (Exception e) {
|
||||
* throw new UndeclaredThrowableException(e); // wraps a checked exception
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* <p>
|
||||
* One downside to using this approach is that the java compiler will not
|
||||
* allow invoking code to specify a checked exception in a catch clause
|
||||
* unless there is some code path within the try block that has invoked a
|
||||
* method declared with that checked exception. If the invoking site wishes
|
||||
* to catch the shaded checked exception, it must either invoke the shaded
|
||||
* code through a method re-declaring the desired checked exception, or
|
||||
* catch Exception and use the instanceof operator. Either of these
|
||||
* techniques are required when interacting with non-java jvm code such as
|
||||
* Jython, Scala, or Groovy, since these languages do not consider any
|
||||
* exceptions as checked.
|
||||
*
|
||||
* @param throwable
|
||||
* The throwable to rethrow.
|
||||
* @param <R> The type of the returned value.
|
||||
* @return Never actually returned, this generic type matches any type
|
||||
* which the calling site requires. "Returning" the results of this
|
||||
* method, as done in the propagateExample above, will satisfy the
|
||||
* java compiler requirement that all code paths return a value.
|
||||
* @since 3.5
|
||||
* @see #wrapAndThrow(Throwable)
|
||||
*/
|
||||
public static <R> R rethrow(final Throwable throwable) {
|
||||
// claim that the typeErasure invocation throws a RuntimeException
|
||||
return ExceptionUtils.<R, RuntimeException> typeErasure(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a Throwable is another Exception type using type erasure. This
|
||||
* hides a checked exception from the java compiler, allowing a checked
|
||||
* exception to be thrown without having the exception in the method's throw
|
||||
* clause.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <R, T extends Throwable> R typeErasure(final Throwable throwable) throws T {
|
||||
throw (T) throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a checked exception without adding the exception to the throws
|
||||
* clause of the calling method. For checked exceptions, this method throws
|
||||
* an UndeclaredThrowableException wrapping the checked exception. For
|
||||
* Errors and RuntimeExceptions, the original exception is rethrown.
|
||||
* <p>
|
||||
* The downside to using this approach is that invoking code which needs to
|
||||
* handle specific checked exceptions must sniff up the exception chain to
|
||||
* determine if the caught exception was caused by the checked exception.
|
||||
*
|
||||
* @param throwable
|
||||
* The throwable to rethrow.
|
||||
* @param <R> The type of the returned value.
|
||||
* @return Never actually returned, this generic type matches any type
|
||||
* which the calling site requires. "Returning" the results of this
|
||||
* method will satisfy the java compiler requirement that all code
|
||||
* paths return a value.
|
||||
* @since 3.5
|
||||
* @see #rethrow(Throwable)
|
||||
* @see #hasCause(Throwable, Class)
|
||||
*/
|
||||
public static <R> R wrapAndThrow(final Throwable throwable) {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
throw (RuntimeException) throwable;
|
||||
}
|
||||
if (throwable instanceof Error) {
|
||||
throw (Error) throwable;
|
||||
}
|
||||
throw new UndeclaredThrowableException(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the throwable's causal chain have an immediate or wrapped exception
|
||||
* of the given type?
|
||||
*
|
||||
* @param chain
|
||||
* The root of a Throwable causal chain.
|
||||
* @param type
|
||||
* The exception type to test.
|
||||
* @return true, if chain is an instance of type or is an
|
||||
* UndeclaredThrowableException wrapping a cause.
|
||||
* @since 3.5
|
||||
* @see #wrapAndThrow(Throwable)
|
||||
*/
|
||||
public static boolean hasCause(Throwable chain,
|
||||
final Class<? extends Throwable> type) {
|
||||
if (chain instanceof UndeclaredThrowableException) {
|
||||
chain = chain.getCause();
|
||||
}
|
||||
return type.isInstance(chain);
|
||||
}
|
||||
}
|
@ -1,254 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.math;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>Provides IEEE-754r variants of NumberUtils methods. </p>
|
||||
*
|
||||
* <p>See: <a href="http://en.wikipedia.org/wiki/IEEE_754r">http://en.wikipedia.org/wiki/IEEE_754r</a></p>
|
||||
*
|
||||
* @since 2.4
|
||||
*/
|
||||
public class IEEE754rUtils {
|
||||
|
||||
/**
|
||||
* <p>Returns the minimum value in an array.</p>
|
||||
*
|
||||
* @param array an array, must not be null or empty
|
||||
* @return the minimum value in the array
|
||||
* @throws IllegalArgumentException if <code>array</code> is <code>null</code>
|
||||
* @throws IllegalArgumentException if <code>array</code> is empty
|
||||
* @since 3.4 Changed signature from min(double[]) to min(double...)
|
||||
*/
|
||||
public static double min(final double... array) {
|
||||
Validate.isTrue(array != null, "The Array must not be null");
|
||||
Validate.isTrue(array.length != 0, "Array cannot be empty.");
|
||||
|
||||
// Finds and returns min
|
||||
double min = array[0];
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
min = min(array[i], min);
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the minimum value in an array.</p>
|
||||
*
|
||||
* @param array an array, must not be null or empty
|
||||
* @return the minimum value in the array
|
||||
* @throws IllegalArgumentException if <code>array</code> is <code>null</code>
|
||||
* @throws IllegalArgumentException if <code>array</code> is empty
|
||||
* @since 3.4 Changed signature from min(float[]) to min(float...)
|
||||
*/
|
||||
public static float min(final float... array) {
|
||||
Validate.isTrue(array != null, "The Array must not be null");
|
||||
Validate.isTrue(array.length != 0, "Array cannot be empty.");
|
||||
|
||||
// Finds and returns min
|
||||
float min = array[0];
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
min = min(array[i], min);
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the minimum of three <code>double</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @param c value 3
|
||||
* @return the smallest of the values
|
||||
*/
|
||||
public static double min(final double a, final double b, final double c) {
|
||||
return min(min(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the minimum of two <code>double</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @return the smallest of the values
|
||||
*/
|
||||
public static double min(final double a, final double b) {
|
||||
if(Double.isNaN(a)) {
|
||||
return b;
|
||||
} else
|
||||
if(Double.isNaN(b)) {
|
||||
return a;
|
||||
} else {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the minimum of three <code>float</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @param c value 3
|
||||
* @return the smallest of the values
|
||||
*/
|
||||
public static float min(final float a, final float b, final float c) {
|
||||
return min(min(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the minimum of two <code>float</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @return the smallest of the values
|
||||
*/
|
||||
public static float min(final float a, final float b) {
|
||||
if(Float.isNaN(a)) {
|
||||
return b;
|
||||
} else
|
||||
if(Float.isNaN(b)) {
|
||||
return a;
|
||||
} else {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the maximum value in an array.</p>
|
||||
*
|
||||
* @param array an array, must not be null or empty
|
||||
* @return the minimum value in the array
|
||||
* @throws IllegalArgumentException if <code>array</code> is <code>null</code>
|
||||
* @throws IllegalArgumentException if <code>array</code> is empty
|
||||
* @since 3.4 Changed signature from max(double[]) to max(double...)
|
||||
*/
|
||||
public static double max(final double... array) {
|
||||
Validate.isTrue(array != null, "The Array must not be null");
|
||||
Validate.isTrue(array.length != 0, "Array cannot be empty.");
|
||||
|
||||
// Finds and returns max
|
||||
double max = array[0];
|
||||
for (int j = 1; j < array.length; j++) {
|
||||
max = max(array[j], max);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the maximum value in an array.</p>
|
||||
*
|
||||
* @param array an array, must not be null or empty
|
||||
* @return the minimum value in the array
|
||||
* @throws IllegalArgumentException if <code>array</code> is <code>null</code>
|
||||
* @throws IllegalArgumentException if <code>array</code> is empty
|
||||
* @since 3.4 Changed signature from max(float[]) to max(float...)
|
||||
*/
|
||||
public static float max(final float... array) {
|
||||
Validate.isTrue(array != null, "The Array must not be null");
|
||||
Validate.isTrue(array.length != 0, "Array cannot be empty.");
|
||||
|
||||
// Finds and returns max
|
||||
float max = array[0];
|
||||
for (int j = 1; j < array.length; j++) {
|
||||
max = max(array[j], max);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the maximum of three <code>double</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @param c value 3
|
||||
* @return the largest of the values
|
||||
*/
|
||||
public static double max(final double a, final double b, final double c) {
|
||||
return max(max(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the maximum of two <code>double</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @return the largest of the values
|
||||
*/
|
||||
public static double max(final double a, final double b) {
|
||||
if(Double.isNaN(a)) {
|
||||
return b;
|
||||
} else
|
||||
if(Double.isNaN(b)) {
|
||||
return a;
|
||||
} else {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the maximum of three <code>float</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @param c value 3
|
||||
* @return the largest of the values
|
||||
*/
|
||||
public static float max(final float a, final float b, final float c) {
|
||||
return max(max(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the maximum of two <code>float</code> values.</p>
|
||||
*
|
||||
* <p>NaN is only returned if all numbers are NaN as per IEEE-754r. </p>
|
||||
*
|
||||
* @param a value 1
|
||||
* @param b value 2
|
||||
* @return the largest of the values
|
||||
*/
|
||||
public static float max(final float a, final float b) {
|
||||
if(Float.isNaN(a)) {
|
||||
return b;
|
||||
} else
|
||||
if(Float.isNaN(b)) {
|
||||
return a;
|
||||
} else {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.mutable;
|
||||
|
||||
/**
|
||||
* Provides mutable access to a value.
|
||||
* <p>
|
||||
* <code>Mutable</code> is used as a generic interface to the implementations in this package.
|
||||
* <p>
|
||||
* A typical use case would be to enable a primitive or string to be passed to a method and allow that method to
|
||||
* effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in
|
||||
* a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects.
|
||||
*
|
||||
* @param <T> the type to set and get
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface Mutable<T> {
|
||||
|
||||
/**
|
||||
* Gets the value of this mutable.
|
||||
*
|
||||
* @return the stored value
|
||||
*/
|
||||
T getValue();
|
||||
|
||||
/**
|
||||
* Sets the value of this mutable.
|
||||
*
|
||||
* @param value
|
||||
* the value to store
|
||||
* @throws NullPointerException
|
||||
* if the object is null and null is invalid
|
||||
* @throws ClassCastException
|
||||
* if the type is invalid
|
||||
*/
|
||||
void setValue(T value);
|
||||
|
||||
}
|
@ -1,382 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.mutable;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.math.NumberUtils;
|
||||
|
||||
/**
|
||||
* A mutable <code>int</code> wrapper.
|
||||
* <p>
|
||||
* Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter.
|
||||
*
|
||||
* @see Integer
|
||||
* @since 2.1
|
||||
*/
|
||||
public class MutableInt extends Number implements Comparable<MutableInt>, Mutable<Number> {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 512176391864L;
|
||||
|
||||
/** The mutable value. */
|
||||
private int value;
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt with the default value of zero.
|
||||
*/
|
||||
public MutableInt() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt with the specified value.
|
||||
*
|
||||
* @param value the initial value to store
|
||||
*/
|
||||
public MutableInt(final int value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt with the specified value.
|
||||
*
|
||||
* @param value the initial value to store, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public MutableInt(final Number value) {
|
||||
super();
|
||||
this.value = value.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt parsing the given string.
|
||||
*
|
||||
* @param value the string to parse, not null
|
||||
* @throws NumberFormatException if the string cannot be parsed into an int
|
||||
* @since 2.5
|
||||
*/
|
||||
public MutableInt(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Integer.parseInt(value);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets the value as a Integer instance.
|
||||
*
|
||||
* @return the value as a Integer, never null
|
||||
*/
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return Integer.valueOf(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void setValue(final int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value from any Number instance.
|
||||
*
|
||||
* @param value the value to set, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
@Override
|
||||
public void setValue(final Number value) {
|
||||
this.value = value.intValue();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Increments the value.
|
||||
*
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void increment() {
|
||||
value++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this instance's value by 1; this method returns the value associated with the instance
|
||||
* immediately prior to the increment operation. This method is not thread safe.
|
||||
*
|
||||
* @return the value associated with the instance before it was incremented
|
||||
* @since 3.5
|
||||
*/
|
||||
public int getAndIncrement() {
|
||||
final int last = value;
|
||||
value++;
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this instance's value by 1; this method returns the value associated with the instance
|
||||
* immediately after the increment operation. This method is not thread safe.
|
||||
*
|
||||
* @return the value associated with the instance after it is incremented
|
||||
* @since 3.5
|
||||
*/
|
||||
public int incrementAndGet() {
|
||||
value++;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the value.
|
||||
*
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void decrement() {
|
||||
value--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements this instance's value by 1; this method returns the value associated with the instance
|
||||
* immediately prior to the decrement operation. This method is not thread safe.
|
||||
*
|
||||
* @return the value associated with the instance before it was decremented
|
||||
* @since 3.5
|
||||
*/
|
||||
public int getAndDecrement() {
|
||||
final int last = value;
|
||||
value--;
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements this instance's value by 1; this method returns the value associated with the instance
|
||||
* immediately after the decrement operation. This method is not thread safe.
|
||||
*
|
||||
* @return the value associated with the instance after it is decremented
|
||||
* @since 3.5
|
||||
*/
|
||||
public int decrementAndGet() {
|
||||
value--;
|
||||
return value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Adds a value to the value of this instance.
|
||||
*
|
||||
* @param operand the value to add, not null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void add(final int operand) {
|
||||
this.value += operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to the value of this instance.
|
||||
*
|
||||
* @param operand the value to add, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void add(final Number operand) {
|
||||
this.value += operand.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a value from the value of this instance.
|
||||
*
|
||||
* @param operand the value to subtract, not null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void subtract(final int operand) {
|
||||
this.value -= operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a value from the value of this instance.
|
||||
*
|
||||
* @param operand the value to subtract, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void subtract(final Number operand) {
|
||||
this.value -= operand.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this instance's value by {@code operand}; this method returns the value associated with the instance
|
||||
* immediately after the addition operation. This method is not thread safe.
|
||||
*
|
||||
* @param operand the quantity to add, not null
|
||||
* @return the value associated with this instance after adding the operand
|
||||
* @since 3.5
|
||||
*/
|
||||
public int addAndGet(final int operand) {
|
||||
this.value += operand;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this instance's value by {@code operand}; this method returns the value associated with the instance
|
||||
* immediately after the addition operation. This method is not thread safe.
|
||||
*
|
||||
* @param operand the quantity to add, not null
|
||||
* @throws NullPointerException if {@code operand} is null
|
||||
* @return the value associated with this instance after adding the operand
|
||||
* @since 3.5
|
||||
*/
|
||||
public int addAndGet(final Number operand) {
|
||||
this.value += operand.intValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this instance's value by {@code operand}; this method returns the value associated with the instance
|
||||
* immediately prior to the addition operation. This method is not thread safe.
|
||||
*
|
||||
* @param operand the quantity to add, not null
|
||||
* @return the value associated with this instance immediately before the operand was added
|
||||
* @since 3.5
|
||||
*/
|
||||
public int getAndAdd(final int operand) {
|
||||
final int last = value;
|
||||
this.value += operand;
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this instance's value by {@code operand}; this method returns the value associated with the instance
|
||||
* immediately prior to the addition operation. This method is not thread safe.
|
||||
*
|
||||
* @param operand the quantity to add, not null
|
||||
* @throws NullPointerException if {@code operand} is null
|
||||
* @return the value associated with this instance immediately before the operand was added
|
||||
* @since 3.5
|
||||
*/
|
||||
public int getAndAdd(final Number operand) {
|
||||
final int last = value;
|
||||
this.value += operand.intValue();
|
||||
return last;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// shortValue and byteValue rely on Number implementation
|
||||
/**
|
||||
* Returns the value of this MutableInt as an int.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type int.
|
||||
*/
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this MutableInt as a long.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type long.
|
||||
*/
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this MutableInt as a float.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type float.
|
||||
*/
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this MutableInt as a double.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type double.
|
||||
*/
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets this mutable as an instance of Integer.
|
||||
*
|
||||
* @return a Integer instance containing the value from this mutable, never null
|
||||
*/
|
||||
public Integer toInteger() {
|
||||
return Integer.valueOf(intValue());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Compares this object to the specified object. The result is <code>true</code> if and only if the argument is
|
||||
* not <code>null</code> and is a <code>MutableInt</code> object that contains the same <code>int</code> value
|
||||
* as this object.
|
||||
*
|
||||
* @param obj the object to compare with, null returns false
|
||||
* @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof MutableInt) {
|
||||
return value == ((MutableInt) obj).intValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitable hash code for this mutable.
|
||||
*
|
||||
* @return a suitable hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Compares this mutable to another in ascending order.
|
||||
*
|
||||
* @param other the other mutable to compare to, not null
|
||||
* @return negative if this is less, zero if equal, positive if greater
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(final MutableInt other) {
|
||||
return NumberUtils.compare(this.value, other.value);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the String value of this mutable.
|
||||
*
|
||||
* @return the mutable value as a string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.qihoo360.replugin.ext.lang3.mutable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A mutable <code>Object</code> wrapper.
|
||||
*
|
||||
* @param <T> the type to set and get
|
||||
* @since 2.1
|
||||
*/
|
||||
public class MutableObject<T> implements Mutable<T>, Serializable {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 86241875189L;
|
||||
|
||||
/** The mutable value. */
|
||||
private T value;
|
||||
|
||||
/**
|
||||
* Constructs a new MutableObject with the default value of <code>null</code>.
|
||||
*/
|
||||
public MutableObject() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableObject with the specified value.
|
||||
*
|
||||
* @param value the initial value to store
|
||||
*/
|
||||
public MutableObject(final T value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets the value.
|
||||
*
|
||||
* @return the value, may be null
|
||||
*/
|
||||
@Override
|
||||
public T getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
@Override
|
||||
public void setValue(final T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>
|
||||
* Compares this object against the specified object. The result is <code>true</code> if and only if the argument
|
||||
* is not <code>null</code> and is a <code>MutableObject</code> object that contains the same <code>T</code>
|
||||
* value as this object.
|
||||
* </p>
|
||||
*
|
||||
* @param obj the object to compare with, <code>null</code> returns <code>false</code>
|
||||
* @return <code>true</code> if the objects are the same;
|
||||
* <code>true</code> if the objects have equivalent <code>value</code> fields;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (this.getClass() == obj.getClass()) {
|
||||
final MutableObject<?> that = (MutableObject<?>) obj;
|
||||
return this.value.equals(that.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value's hash code or <code>0</code> if the value is <code>null</code>.
|
||||
*
|
||||
* @return the value's hash code or <code>0</code> if the value is <code>null</code>.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value == null ? 0 : value.hashCode();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the String value of this mutable.
|
||||
*
|
||||
* @return the mutable value as a string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return value == null ? "null" : value.toString();
|
||||
}
|
||||
|
||||
}
|
@ -1,311 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.reflect;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ArrayUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p> Utility reflection methods focused on constructors, modeled after
|
||||
* {@link MethodUtils}. </p>
|
||||
*
|
||||
* <h3>Known Limitations</h3> <h4>Accessing Public Constructors In A Default
|
||||
* Access Superclass</h4> <p>There is an issue when invoking {@code public} constructors
|
||||
* contained in a default access superclass. Reflection correctly locates these
|
||||
* constructors and assigns them as {@code public}. However, an
|
||||
* {@link IllegalAccessException} is thrown if the constructor is
|
||||
* invoked.</p>
|
||||
*
|
||||
* <p>{@link ConstructorUtils} contains a workaround for this situation: it
|
||||
* will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this constructor. If this
|
||||
* call succeeds, then the method can be invoked as normal. This call will only
|
||||
* succeed when the application has sufficient security privileges. If this call
|
||||
* fails then a warning will be logged and the method may fail.</p>
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public class ConstructorUtils {
|
||||
|
||||
/**
|
||||
* <p>ConstructorUtils instances should NOT be constructed in standard
|
||||
* programming. Instead, the class should be used as
|
||||
* {@code ConstructorUtils.invokeConstructor(cls, args)}.</p>
|
||||
*
|
||||
* <p>This constructor is {@code public} to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public ConstructorUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new instance of the specified class inferring the right constructor
|
||||
* from the types of the arguments.</p>
|
||||
*
|
||||
* <p>This locates and calls a constructor.
|
||||
* The constructor signature must match the argument types by assignment compatibility.</p>
|
||||
*
|
||||
* @param <T> the type to be constructed
|
||||
* @param cls the class to be constructed, not {@code null}
|
||||
* @param args the array of arguments, {@code null} treated as empty
|
||||
* @return new instance of {@code cls}, not {@code null}
|
||||
*
|
||||
* @throws NullPointerException if {@code cls} is {@code null}
|
||||
* @throws NoSuchMethodException if a matching constructor cannot be found
|
||||
* @throws IllegalAccessException if invocation is not permitted by security
|
||||
* @throws InvocationTargetException if an error occurs on invocation
|
||||
* @throws InstantiationException if an error occurs on instantiation
|
||||
* @see #invokeConstructor(Class, Object[], Class[])
|
||||
*/
|
||||
public static <T> T invokeConstructor(final Class<T> cls, Object... args)
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
|
||||
InstantiationException {
|
||||
args = ArrayUtils.nullToEmpty(args);
|
||||
final Class<?> parameterTypes[] = ClassUtils.toClass(args);
|
||||
return invokeConstructor(cls, args, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new instance of the specified class choosing the right constructor
|
||||
* from the list of parameter types.</p>
|
||||
*
|
||||
* <p>This locates and calls a constructor.
|
||||
* The constructor signature must match the parameter types by assignment compatibility.</p>
|
||||
*
|
||||
* @param <T> the type to be constructed
|
||||
* @param cls the class to be constructed, not {@code null}
|
||||
* @param args the array of arguments, {@code null} treated as empty
|
||||
* @param parameterTypes the array of parameter types, {@code null} treated as empty
|
||||
* @return new instance of {@code cls}, not {@code null}
|
||||
*
|
||||
* @throws NullPointerException if {@code cls} is {@code null}
|
||||
* @throws NoSuchMethodException if a matching constructor cannot be found
|
||||
* @throws IllegalAccessException if invocation is not permitted by security
|
||||
* @throws InvocationTargetException if an error occurs on invocation
|
||||
* @throws InstantiationException if an error occurs on instantiation
|
||||
* @see Constructor#newInstance
|
||||
*/
|
||||
public static <T> T invokeConstructor(final Class<T> cls, Object[] args, Class<?>[] parameterTypes)
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
|
||||
InstantiationException {
|
||||
args = ArrayUtils.nullToEmpty(args);
|
||||
parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
|
||||
final Constructor<T> ctor = getMatchingAccessibleConstructor(cls, parameterTypes);
|
||||
if (ctor == null) {
|
||||
throw new NoSuchMethodException(
|
||||
"No such accessible constructor on object: " + cls.getName());
|
||||
}
|
||||
if (ctor.isVarArgs()) {
|
||||
final Class<?>[] methodParameterTypes = ctor.getParameterTypes();
|
||||
args = MethodUtils.getVarArgs(args, methodParameterTypes);
|
||||
}
|
||||
return ctor.newInstance(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new instance of the specified class inferring the right constructor
|
||||
* from the types of the arguments.</p>
|
||||
*
|
||||
* <p>This locates and calls a constructor.
|
||||
* The constructor signature must match the argument types exactly.</p>
|
||||
*
|
||||
* @param <T> the type to be constructed
|
||||
* @param cls the class to be constructed, not {@code null}
|
||||
* @param args the array of arguments, {@code null} treated as empty
|
||||
* @return new instance of {@code cls}, not {@code null}
|
||||
*
|
||||
* @throws NullPointerException if {@code cls} is {@code null}
|
||||
* @throws NoSuchMethodException if a matching constructor cannot be found
|
||||
* @throws IllegalAccessException if invocation is not permitted by security
|
||||
* @throws InvocationTargetException if an error occurs on invocation
|
||||
* @throws InstantiationException if an error occurs on instantiation
|
||||
* @see #invokeExactConstructor(Class, Object[], Class[])
|
||||
*/
|
||||
public static <T> T invokeExactConstructor(final Class<T> cls, Object... args)
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
|
||||
InstantiationException {
|
||||
args = ArrayUtils.nullToEmpty(args);
|
||||
final Class<?> parameterTypes[] = ClassUtils.toClass(args);
|
||||
return invokeExactConstructor(cls, args, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new instance of the specified class choosing the right constructor
|
||||
* from the list of parameter types.</p>
|
||||
*
|
||||
* <p>This locates and calls a constructor.
|
||||
* The constructor signature must match the parameter types exactly.</p>
|
||||
*
|
||||
* @param <T> the type to be constructed
|
||||
* @param cls the class to be constructed, not {@code null}
|
||||
* @param args the array of arguments, {@code null} treated as empty
|
||||
* @param parameterTypes the array of parameter types, {@code null} treated as empty
|
||||
* @return new instance of <code>cls</code>, not {@code null}
|
||||
*
|
||||
* @throws NullPointerException if {@code cls} is {@code null}
|
||||
* @throws NoSuchMethodException if a matching constructor cannot be found
|
||||
* @throws IllegalAccessException if invocation is not permitted by security
|
||||
* @throws InvocationTargetException if an error occurs on invocation
|
||||
* @throws InstantiationException if an error occurs on instantiation
|
||||
* @see Constructor#newInstance
|
||||
*/
|
||||
public static <T> T invokeExactConstructor(final Class<T> cls, Object[] args,
|
||||
Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException, InstantiationException {
|
||||
args = ArrayUtils.nullToEmpty(args);
|
||||
parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
|
||||
final Constructor<T> ctor = getAccessibleConstructor(cls, parameterTypes);
|
||||
if (ctor == null) {
|
||||
throw new NoSuchMethodException(
|
||||
"No such accessible constructor on object: "+ cls.getName());
|
||||
}
|
||||
return ctor.newInstance(args);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Finds a constructor given a class and signature, checking accessibility.</p>
|
||||
*
|
||||
* <p>This finds the constructor and ensures that it is accessible.
|
||||
* The constructor signature must match the parameter types exactly.</p>
|
||||
*
|
||||
* @param <T> the constructor type
|
||||
* @param cls the class to find a constructor for, not {@code null}
|
||||
* @param parameterTypes the array of parameter types, {@code null} treated as empty
|
||||
* @return the constructor, {@code null} if no matching accessible constructor found
|
||||
* @see Class#getConstructor
|
||||
* @see #getAccessibleConstructor(Constructor)
|
||||
* @throws NullPointerException if {@code cls} is {@code null}
|
||||
*/
|
||||
public static <T> Constructor<T> getAccessibleConstructor(final Class<T> cls,
|
||||
final Class<?>... parameterTypes) {
|
||||
Validate.notNull(cls, "class cannot be null");
|
||||
try {
|
||||
return getAccessibleConstructor(cls.getConstructor(parameterTypes));
|
||||
} catch (final NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks if the specified constructor is accessible.</p>
|
||||
*
|
||||
* <p>This simply ensures that the constructor is accessible.</p>
|
||||
*
|
||||
* @param <T> the constructor type
|
||||
* @param ctor the prototype constructor object, not {@code null}
|
||||
* @return the constructor, {@code null} if no matching accessible constructor found
|
||||
* @see SecurityManager
|
||||
* @throws NullPointerException if {@code ctor} is {@code null}
|
||||
*/
|
||||
public static <T> Constructor<T> getAccessibleConstructor(final Constructor<T> ctor) {
|
||||
Validate.notNull(ctor, "constructor cannot be null");
|
||||
return MemberUtils.isAccessible(ctor)
|
||||
&& isAccessible(ctor.getDeclaringClass()) ? ctor : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds an accessible constructor with compatible parameters.</p>
|
||||
*
|
||||
* <p>This checks all the constructor and finds one with compatible parameters
|
||||
* This requires that every parameter is assignable from the given parameter types.
|
||||
* This is a more flexible search than the normal exact matching algorithm.</p>
|
||||
*
|
||||
* <p>First it checks if there is a constructor matching the exact signature.
|
||||
* If not then all the constructors of the class are checked to see if their
|
||||
* signatures are assignment-compatible with the parameter types.
|
||||
* The first assignment-compatible matching constructor is returned.</p>
|
||||
*
|
||||
* @param <T> the constructor type
|
||||
* @param cls the class to find a constructor for, not {@code null}
|
||||
* @param parameterTypes find method with compatible parameters
|
||||
* @return the constructor, null if no matching accessible constructor found
|
||||
* @throws NullPointerException if {@code cls} is {@code null}
|
||||
*/
|
||||
public static <T> Constructor<T> getMatchingAccessibleConstructor(final Class<T> cls,
|
||||
final Class<?>... parameterTypes) {
|
||||
Validate.notNull(cls, "class cannot be null");
|
||||
// see if we can find the constructor directly
|
||||
// most of the time this works and it's much faster
|
||||
try {
|
||||
final Constructor<T> ctor = cls.getConstructor(parameterTypes);
|
||||
MemberUtils.setAccessibleWorkaround(ctor);
|
||||
return ctor;
|
||||
} catch (final NoSuchMethodException e) { // NOPMD - Swallow
|
||||
}
|
||||
Constructor<T> result = null;
|
||||
/*
|
||||
* (1) Class.getConstructors() is documented to return Constructor<T> so as
|
||||
* long as the array is not subsequently modified, everything's fine.
|
||||
*/
|
||||
final Constructor<?>[] ctors = cls.getConstructors();
|
||||
|
||||
// return best match:
|
||||
for (Constructor<?> ctor : ctors) {
|
||||
// compare parameters
|
||||
if (MemberUtils.isMatchingConstructor(ctor, parameterTypes)) {
|
||||
// get accessible version of constructor
|
||||
ctor = getAccessibleConstructor(ctor);
|
||||
if (ctor != null) {
|
||||
MemberUtils.setAccessibleWorkaround(ctor);
|
||||
if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) {
|
||||
// temporary variable for annotation, see comment above (1)
|
||||
@SuppressWarnings("unchecked")
|
||||
final
|
||||
Constructor<T> constructor = (Constructor<T>)ctor;
|
||||
result = constructor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Learn whether the specified class is generally accessible, i.e. is
|
||||
* declared in an entirely {@code public} manner.
|
||||
* @param type to check
|
||||
* @return {@code true} if {@code type} and any enclosing classes are
|
||||
* {@code public}.
|
||||
*/
|
||||
private static boolean isAccessible(final Class<?> type) {
|
||||
Class<?> cls = type;
|
||||
while (cls != null) {
|
||||
if (!Modifier.isPublic(cls.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
cls = cls.getEnclosingClass();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,839 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.reflect;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.StringUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for working with {@link Field}s by reflection. Adapted and refactored from the dormant [reflect] Commons
|
||||
* sandbox component.
|
||||
* <p>
|
||||
* The ability is provided to break the scoping restrictions coded by the programmer. This can allow fields to be
|
||||
* changed that shouldn't be. This facility should be used with care.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public class FieldUtils {
|
||||
|
||||
/**
|
||||
* {@link FieldUtils} instances should NOT be constructed in standard programming.
|
||||
* <p>
|
||||
* This constructor is {@code public} to permit tools that require a JavaBean instance to operate.
|
||||
* </p>
|
||||
*/
|
||||
public FieldUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessible {@link Field} by name respecting scope. Superclasses/interfaces will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty
|
||||
*/
|
||||
public static Field getField(final Class<?> cls, final String fieldName) {
|
||||
final Field field = getField(cls, fieldName, false);
|
||||
MemberUtils.setAccessibleWorkaround(field);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessible {@link Field} by name, breaking scope if requested. Superclasses/interfaces will be
|
||||
* considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty or is matched at multiple places
|
||||
* in the inheritance hierarchy
|
||||
*/
|
||||
public static Field getField(final Class<?> cls, final String fieldName, final boolean forceAccess) {
|
||||
Validate.isTrue(cls != null, "The class must not be null");
|
||||
Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty");
|
||||
// FIXME is this workaround still needed? lang requires Java 6
|
||||
// Sun Java 1.3 has a bugged implementation of getField hence we write the
|
||||
// code ourselves
|
||||
|
||||
// getField() will return the Field object with the declaring class
|
||||
// set correctly to the class that declares the field. Thus requesting the
|
||||
// field on a subclass will return the field from the superclass.
|
||||
//
|
||||
// priority order for lookup:
|
||||
// searchclass private/protected/package/public
|
||||
// superclass protected/package/public
|
||||
// private/different package blocks access to further superclasses
|
||||
// implementedinterface public
|
||||
|
||||
// check up the superclass hierarchy
|
||||
for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
|
||||
try {
|
||||
final Field field = acls.getDeclaredField(fieldName);
|
||||
// getDeclaredField checks for non-public scopes as well
|
||||
// and it returns accurate results
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
if (forceAccess) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
} catch (final NoSuchFieldException ex) { // NOPMD
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// check the public interface case. This must be manually searched for
|
||||
// incase there is a public supersuperclass field hidden by a private/package
|
||||
// superclass field.
|
||||
Field match = null;
|
||||
for (final Class<?> class1 : ClassUtils.getAllInterfaces(cls)) {
|
||||
try {
|
||||
final Field test = class1.getField(fieldName);
|
||||
Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s"
|
||||
+ "; a matching field exists on two or more implemented interfaces.", fieldName, cls);
|
||||
match = test;
|
||||
} catch (final NoSuchFieldException ex) { // NOPMD
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessible {@link Field} by name respecting scope. Only the specified class will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty
|
||||
*/
|
||||
public static Field getDeclaredField(final Class<?> cls, final String fieldName) {
|
||||
return getDeclaredField(cls, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessible {@link Field} by name, breaking scope if requested. Only the specified class will be
|
||||
* considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty
|
||||
*/
|
||||
public static Field getDeclaredField(final Class<?> cls, final String fieldName, final boolean forceAccess) {
|
||||
Validate.isTrue(cls != null, "The class must not be null");
|
||||
Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty");
|
||||
try {
|
||||
// only consider the specified class by using getDeclaredField()
|
||||
final Field field = cls.getDeclaredField(fieldName);
|
||||
if (!MemberUtils.isAccessible(field)) {
|
||||
if (forceAccess) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
} catch (final NoSuchFieldException e) { // NOPMD
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all fields of the given class and its parents (if any).
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to query
|
||||
* @return an array of Fields (possibly empty).
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}
|
||||
* @since 3.2
|
||||
*/
|
||||
public static Field[] getAllFields(final Class<?> cls) {
|
||||
final List<Field> allFieldsList = getAllFieldsList(cls);
|
||||
return allFieldsList.toArray(new Field[allFieldsList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all fields of the given class and its parents (if any).
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to query
|
||||
* @return an array of Fields (possibly empty).
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}
|
||||
* @since 3.2
|
||||
*/
|
||||
public static List<Field> getAllFieldsList(final Class<?> cls) {
|
||||
Validate.isTrue(cls != null, "The class must not be null");
|
||||
final List<Field> allFields = new ArrayList<>();
|
||||
Class<?> currentClass = cls;
|
||||
while (currentClass != null) {
|
||||
final Field[] declaredFields = currentClass.getDeclaredFields();
|
||||
for (final Field field : declaredFields) {
|
||||
allFields.add(field);
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
}
|
||||
return allFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all fields of the given class and its parents (if any) that are annotated with the given annotation.
|
||||
* @param cls
|
||||
* the {@link Class} to query
|
||||
* @param annotationCls
|
||||
* the {@link Annotation} that must be present on a field to be matched
|
||||
* @return an array of Fields (possibly empty).
|
||||
* @throws IllegalArgumentException
|
||||
* if the class or annotation are {@code null}
|
||||
* @since 3.4
|
||||
*/
|
||||
public static Field[] getFieldsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
|
||||
final List<Field> annotatedFieldsList = getFieldsListWithAnnotation(cls, annotationCls);
|
||||
return annotatedFieldsList.toArray(new Field[annotatedFieldsList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all fields of the given class and its parents (if any) that are annotated with the given annotation.
|
||||
* @param cls
|
||||
* the {@link Class} to query
|
||||
* @param annotationCls
|
||||
* the {@link Annotation} that must be present on a field to be matched
|
||||
* @return a list of Fields (possibly empty).
|
||||
* @throws IllegalArgumentException
|
||||
* if the class or annotation are {@code null}
|
||||
* @since 3.4
|
||||
*/
|
||||
public static List<Field> getFieldsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
|
||||
Validate.isTrue(annotationCls != null, "The annotation class must not be null");
|
||||
final List<Field> allFields = getAllFieldsList(cls);
|
||||
final List<Field> annotatedFields = new ArrayList<>();
|
||||
for (final Field field : allFields) {
|
||||
if (field.getAnnotation(annotationCls) != null) {
|
||||
annotatedFields.add(field);
|
||||
}
|
||||
}
|
||||
return annotatedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an accessible {@code static} {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to read
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null}, or not {@code static}
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not accessible
|
||||
*/
|
||||
public static Object readStaticField(final Field field) throws IllegalAccessException {
|
||||
return readStaticField(field, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a static {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to read
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method.
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null} or not {@code static}
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException {
|
||||
Validate.isTrue(field != null, "The field must not be null");
|
||||
Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field '%s' is not static", field.getName());
|
||||
return readField(field, (Object) null, forceAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the named {@code public static} {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @return the value of the field
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
|
||||
* not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not accessible
|
||||
*/
|
||||
public static Object readStaticField(final Class<?> cls, final String fieldName) throws IllegalAccessException {
|
||||
return readStaticField(cls, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the named {@code static} {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
|
||||
* not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static Object readStaticField(final Class<?> cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
|
||||
final Field field = getField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate field '%s' on %s", fieldName, cls);
|
||||
// already forced access above, don't repeat it here:
|
||||
return readStaticField(field, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a {@code static} {@link Field} by name. The field must be {@code public}. Only the specified
|
||||
* class will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @return the value of the field
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
|
||||
* not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not accessible
|
||||
*/
|
||||
public static Object readDeclaredStaticField(final Class<?> cls, final String fieldName) throws IllegalAccessException {
|
||||
return readDeclaredStaticField(cls, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a {@code static} {@link Field} by name. Only the specified class will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* the {@link Class} to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
|
||||
* not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static Object readDeclaredStaticField(final Class<?> cls, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
|
||||
final Field field = getDeclaredField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
|
||||
// already forced access above, don't repeat it here:
|
||||
return readStaticField(field, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an accessible {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* the field to use
|
||||
* @param target
|
||||
* the object to call on, may be {@code null} for {@code static} fields
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null}
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not accessible
|
||||
*/
|
||||
public static Object readField(final Field field, final Object target) throws IllegalAccessException {
|
||||
return readField(field, target, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* the field to use
|
||||
* @param target
|
||||
* the object to call on, may be {@code null} for {@code static} fields
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method.
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null}
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static Object readField(final Field field, final Object target, final boolean forceAccess) throws IllegalAccessException {
|
||||
Validate.isTrue(field != null, "The field must not be null");
|
||||
if (forceAccess && !field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
MemberUtils.setAccessibleWorkaround(field);
|
||||
}
|
||||
return field.get(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the named {@code public} {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @return the value of the field
|
||||
* @throws IllegalArgumentException
|
||||
* if the class is {@code null}, or the field name is blank or empty or could not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the named field is not {@code public}
|
||||
*/
|
||||
public static Object readField(final Object target, final String fieldName) throws IllegalAccessException {
|
||||
return readField(target, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the named {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, or the field name is blank or empty or could not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the named field is not made accessible
|
||||
*/
|
||||
public static Object readField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
|
||||
Validate.isTrue(target != null, "target object must not be null");
|
||||
final Class<?> cls = target.getClass();
|
||||
final Field field = getField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls);
|
||||
// already forced access above, don't repeat it here:
|
||||
return readField(field, target, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the named {@code public} {@link Field}. Only the class of the specified object will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @return the value of the field
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, or the field name is blank or empty or could not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the named field is not {@code public}
|
||||
*/
|
||||
public static Object readDeclaredField(final Object target, final String fieldName) throws IllegalAccessException {
|
||||
return readDeclaredField(target, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link Field} value by name. Only the class of the specified object will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match public fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, or the field name is blank or empty or could not be found
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static Object readDeclaredField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
|
||||
Validate.isTrue(target != null, "target object must not be null");
|
||||
final Class<?> cls = target.getClass();
|
||||
final Field field = getDeclaredField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls, fieldName);
|
||||
// already forced access above, don't repeat it here:
|
||||
return readField(field, target, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@code public static} {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to write
|
||||
* @param value
|
||||
* to set
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null} or not {@code static}, or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not {@code public} or is {@code final}
|
||||
*/
|
||||
public static void writeStaticField(final Field field, final Object value) throws IllegalAccessException {
|
||||
writeStaticField(field, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a static {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to write
|
||||
* @param value
|
||||
* to set
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null} or not {@code static}, or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible or is {@code final}
|
||||
*/
|
||||
public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException {
|
||||
Validate.isTrue(field != null, "The field must not be null");
|
||||
Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field %s.%s is not static", field.getDeclaringClass().getName(),
|
||||
field.getName());
|
||||
writeField(field, (Object) null, value, forceAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a named {@code public static} {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* {@link Class} on which the field is to be found
|
||||
* @param fieldName
|
||||
* to write
|
||||
* @param value
|
||||
* to set
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
|
||||
* not {@code static}, or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not {@code public} or is {@code final}
|
||||
*/
|
||||
public static void writeStaticField(final Class<?> cls, final String fieldName, final Object value) throws IllegalAccessException {
|
||||
writeStaticField(cls, fieldName, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a named {@code static} {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* {@link Class} on which the field is to be found
|
||||
* @param fieldName
|
||||
* to write
|
||||
* @param value
|
||||
* to set
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
|
||||
* not {@code static}, or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible or is {@code final}
|
||||
*/
|
||||
public static void writeStaticField(final Class<?> cls, final String fieldName, final Object value, final boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
final Field field = getField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls);
|
||||
// already forced access above, don't repeat it here:
|
||||
writeStaticField(field, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a named {@code public static} {@link Field}. Only the specified class will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* {@link Class} on which the field is to be found
|
||||
* @param fieldName
|
||||
* to write
|
||||
* @param value
|
||||
* to set
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
|
||||
* not {@code static}, or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not {@code public} or is {@code final}
|
||||
*/
|
||||
public static void writeDeclaredStaticField(final Class<?> cls, final String fieldName, final Object value) throws IllegalAccessException {
|
||||
writeDeclaredStaticField(cls, fieldName, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a named {@code static} {@link Field}. Only the specified class will be considered.
|
||||
*
|
||||
* @param cls
|
||||
* {@link Class} on which the field is to be found
|
||||
* @param fieldName
|
||||
* to write
|
||||
* @param value
|
||||
* to set
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the {@code AccessibleObject#setAccessible(boolean)} method.
|
||||
* {@code false} will only match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
|
||||
* not {@code static}, or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible or is {@code final}
|
||||
*/
|
||||
public static void writeDeclaredStaticField(final Class<?> cls, final String fieldName, final Object value, final boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
final Field field = getDeclaredField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
|
||||
// already forced access above, don't repeat it here:
|
||||
writeField(field, (Object) null, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an accessible {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to write
|
||||
* @param target
|
||||
* the object to call on, may be {@code null} for {@code static} fields
|
||||
* @param value
|
||||
* to set
|
||||
* @throws IllegalAccessException
|
||||
* if the field or target is {@code null}, the field is not accessible or is {@code final}, or
|
||||
* {@code value} is not assignable
|
||||
*/
|
||||
public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException {
|
||||
writeField(field, target, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to write
|
||||
* @param target
|
||||
* the object to call on, may be {@code null} for {@code static} fields
|
||||
* @param value
|
||||
* to set
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null} or {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible or is {@code final}
|
||||
*/
|
||||
public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
Validate.isTrue(field != null, "The field must not be null");
|
||||
if (forceAccess && !field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
MemberUtils.setAccessibleWorkaround(field);
|
||||
}
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the final modifier from a {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to remove the final modifier
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null}
|
||||
* @since 3.2
|
||||
*/
|
||||
public static void removeFinalModifier(final Field field) {
|
||||
removeFinalModifier(field, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the final modifier from a {@link Field}.
|
||||
*
|
||||
* @param field
|
||||
* to remove the final modifier
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if the field is {@code null}
|
||||
* @since 3.3
|
||||
*/
|
||||
public static void removeFinalModifier(final Field field, final boolean forceAccess) {
|
||||
Validate.isTrue(field != null, "The field must not be null");
|
||||
|
||||
try {
|
||||
if (Modifier.isFinal(field.getModifiers())) {
|
||||
// Do all JREs implement Field with a private ivar called "modifiers"?
|
||||
final Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
final boolean doForceAccess = forceAccess && !modifiersField.isAccessible();
|
||||
if (doForceAccess) {
|
||||
modifiersField.setAccessible(true);
|
||||
}
|
||||
try {
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
} finally {
|
||||
if (doForceAccess) {
|
||||
modifiersField.setAccessible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final NoSuchFieldException ignored) {
|
||||
// The field class contains always a modifiers field
|
||||
} catch (final IllegalAccessException ignored) {
|
||||
// The modifiers field is made accessible
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@code public} {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param value
|
||||
* to set
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
|
||||
* {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not accessible
|
||||
*/
|
||||
public static void writeField(final Object target, final String fieldName, final Object value) throws IllegalAccessException {
|
||||
writeField(target, fieldName, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link Field}. Superclasses will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param value
|
||||
* to set
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
|
||||
* {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static void writeField(final Object target, final String fieldName, final Object value, final boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
Validate.isTrue(target != null, "target object must not be null");
|
||||
final Class<?> cls = target.getClass();
|
||||
final Field field = getField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
|
||||
// already forced access above, don't repeat it here:
|
||||
writeField(field, target, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@code public} {@link Field}. Only the specified class will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param value
|
||||
* to set
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
|
||||
* {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException {
|
||||
writeDeclaredField(target, fieldName, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@code public} {@link Field}. Only the specified class will be considered.
|
||||
*
|
||||
* @param target
|
||||
* the object to reflect, must not be {@code null}
|
||||
* @param fieldName
|
||||
* the field name to obtain
|
||||
* @param value
|
||||
* to set
|
||||
* @param forceAccess
|
||||
* whether to break scope restrictions using the
|
||||
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
|
||||
* match {@code public} fields.
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
|
||||
* {@code value} is not assignable
|
||||
* @throws IllegalAccessException
|
||||
* if the field is not made accessible
|
||||
*/
|
||||
public static void writeDeclaredField(final Object target, final String fieldName, final Object value, final boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
Validate.isTrue(target != null, "target object must not be null");
|
||||
final Class<?> cls = target.getClass();
|
||||
final Field field = getDeclaredField(cls, fieldName, forceAccess);
|
||||
Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName);
|
||||
// already forced access above, don't repeat it here:
|
||||
writeField(field, target, value, false);
|
||||
}
|
||||
}
|
@ -1,320 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.reflect;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
|
||||
/**
|
||||
* Contains common code for working with {@link Method Methods}/{@link Constructor Constructors},
|
||||
* extracted and refactored from {@link MethodUtils} when it was imported from Commons BeanUtils.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class MemberUtils {
|
||||
// TODO extract an interface to implement compareParameterSets(...)?
|
||||
|
||||
private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
|
||||
|
||||
/** Array of primitive number types ordered by "promotability" */
|
||||
private static final Class<?>[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE,
|
||||
Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE };
|
||||
|
||||
/**
|
||||
* XXX Default access superclass workaround.
|
||||
*
|
||||
* When a {@code public} class has a default access superclass with {@code public} members,
|
||||
* these members are accessible. Calling them from compiled code works fine.
|
||||
* Unfortunately, on some JVMs, using reflection to invoke these members
|
||||
* seems to (wrongly) prevent access even when the modifier is {@code public}.
|
||||
* Calling {@code setAccessible(true)} solves the problem but will only work from
|
||||
* sufficiently privileged code. Better workarounds would be gratefully
|
||||
* accepted.
|
||||
* @param o the AccessibleObject to set as accessible
|
||||
* @return a boolean indicating whether the accessibility of the object was set to true.
|
||||
*/
|
||||
static boolean setAccessibleWorkaround(final AccessibleObject o) {
|
||||
if (o == null || o.isAccessible()) {
|
||||
return false;
|
||||
}
|
||||
final Member m = (Member) o;
|
||||
if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) {
|
||||
try {
|
||||
o.setAccessible(true);
|
||||
return true;
|
||||
} catch (final SecurityException e) { // NOPMD
|
||||
// ignore in favor of subsequent IllegalAccessException
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given set of modifiers implies package access.
|
||||
* @param modifiers to test
|
||||
* @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected
|
||||
*/
|
||||
static boolean isPackageAccess(final int modifiers) {
|
||||
return (modifiers & ACCESS_TEST) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a {@link Member} is accessible.
|
||||
* @param m Member to check
|
||||
* @return {@code true} if <code>m</code> is accessible
|
||||
*/
|
||||
static boolean isAccessible(final Member m) {
|
||||
return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the relative fitness of two Constructors in terms of how well they
|
||||
* match a set of runtime parameter types, such that a list ordered
|
||||
* by the results of the comparison would return the best match first
|
||||
* (least).
|
||||
*
|
||||
* @param left the "left" Constructor
|
||||
* @param right the "right" Constructor
|
||||
* @param actual the runtime parameter types to match against
|
||||
* {@code left}/{@code right}
|
||||
* @return int consistent with {@code compare} semantics
|
||||
* @since 3.5
|
||||
*/
|
||||
static int compareConstructorFit(final Constructor<?> left, final Constructor<?> right, final Class<?>[] actual) {
|
||||
return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the relative fitness of two Methods in terms of how well they
|
||||
* match a set of runtime parameter types, such that a list ordered
|
||||
* by the results of the comparison would return the best match first
|
||||
* (least).
|
||||
*
|
||||
* @param left the "left" Method
|
||||
* @param right the "right" Method
|
||||
* @param actual the runtime parameter types to match against
|
||||
* {@code left}/{@code right}
|
||||
* @return int consistent with {@code compare} semantics
|
||||
* @since 3.5
|
||||
*/
|
||||
static int compareMethodFit(final Method left, final Method right, final Class<?>[] actual) {
|
||||
return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the relative fitness of two Executables in terms of how well they
|
||||
* match a set of runtime parameter types, such that a list ordered
|
||||
* by the results of the comparison would return the best match first
|
||||
* (least).
|
||||
*
|
||||
* @param left the "left" Executable
|
||||
* @param right the "right" Executable
|
||||
* @param actual the runtime parameter types to match against
|
||||
* {@code left}/{@code right}
|
||||
* @return int consistent with {@code compare} semantics
|
||||
*/
|
||||
private static int compareParameterTypes(final Executable left, final Executable right, final Class<?>[] actual) {
|
||||
final float leftCost = getTotalTransformationCost(actual, left);
|
||||
final float rightCost = getTotalTransformationCost(actual, right);
|
||||
return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of the object transformation cost for each class in the
|
||||
* source argument list.
|
||||
* @param srcArgs The source arguments
|
||||
* @param executable The executable to calculate transformation costs for
|
||||
* @return The total transformation cost
|
||||
*/
|
||||
private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Executable executable) {
|
||||
final Class<?>[] destArgs = executable.getParameterTypes();
|
||||
final boolean isVarArgs = executable.isVarArgs();
|
||||
|
||||
// "source" and "destination" are the actual and declared args respectively.
|
||||
float totalCost = 0.0f;
|
||||
final long normalArgsLen = isVarArgs ? destArgs.length-1 : destArgs.length;
|
||||
if (srcArgs.length < normalArgsLen) {
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
for (int i = 0; i < normalArgsLen; i++) {
|
||||
totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]);
|
||||
}
|
||||
if (isVarArgs) {
|
||||
// When isVarArgs is true, srcArgs and dstArgs may differ in length.
|
||||
// There are two special cases to consider:
|
||||
final boolean noVarArgsPassed = srcArgs.length < destArgs.length;
|
||||
final boolean explicitArrayForVarags = srcArgs.length == destArgs.length && srcArgs[srcArgs.length-1].isArray();
|
||||
|
||||
final float varArgsCost = 0.001f;
|
||||
final Class<?> destClass = destArgs[destArgs.length-1].getComponentType();
|
||||
if (noVarArgsPassed) {
|
||||
// When no varargs passed, the best match is the most generic matching type, not the most specific.
|
||||
totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost;
|
||||
} else if (explicitArrayForVarags) {
|
||||
final Class<?> sourceClass = srcArgs[srcArgs.length-1].getComponentType();
|
||||
totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost;
|
||||
} else {
|
||||
// This is typical varargs case.
|
||||
for (int i = destArgs.length-1; i < srcArgs.length; i++) {
|
||||
final Class<?> srcClass = srcArgs[i];
|
||||
totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of steps required needed to turn the source class into
|
||||
* the destination class. This represents the number of steps in the object
|
||||
* hierarchy graph.
|
||||
* @param srcClass The source class
|
||||
* @param destClass The destination class
|
||||
* @return The cost of transforming an object
|
||||
*/
|
||||
private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
|
||||
if (destClass.isPrimitive()) {
|
||||
return getPrimitivePromotionCost(srcClass, destClass);
|
||||
}
|
||||
float cost = 0.0f;
|
||||
while (srcClass != null && !destClass.equals(srcClass)) {
|
||||
if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) {
|
||||
// slight penalty for interface match.
|
||||
// we still want an exact match to override an interface match,
|
||||
// but
|
||||
// an interface match should override anything where we have to
|
||||
// get a superclass.
|
||||
cost += 0.25f;
|
||||
break;
|
||||
}
|
||||
cost++;
|
||||
srcClass = srcClass.getSuperclass();
|
||||
}
|
||||
/*
|
||||
* If the destination class is null, we've traveled all the way up to
|
||||
* an Object match. We'll penalize this by adding 1.5 to the cost.
|
||||
*/
|
||||
if (srcClass == null) {
|
||||
cost += 1.5f;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of steps required to promote a primitive number to another
|
||||
* type.
|
||||
* @param srcClass the (primitive) source class
|
||||
* @param destClass the (primitive) destination class
|
||||
* @return The cost of promoting the primitive
|
||||
*/
|
||||
private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
|
||||
float cost = 0.0f;
|
||||
Class<?> cls = srcClass;
|
||||
if (!cls.isPrimitive()) {
|
||||
// slight unwrapping penalty
|
||||
cost += 0.1f;
|
||||
cls = ClassUtils.wrapperToPrimitive(cls);
|
||||
}
|
||||
for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
|
||||
if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
|
||||
cost += 0.1f;
|
||||
if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
|
||||
cls = ORDERED_PRIMITIVE_TYPES[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
static boolean isMatchingMethod(final Method method, final Class<?>[] parameterTypes) {
|
||||
return MemberUtils.isMatchingExecutable(Executable.of(method), parameterTypes);
|
||||
}
|
||||
|
||||
static boolean isMatchingConstructor(final Constructor<?> method, final Class<?>[] parameterTypes) {
|
||||
return MemberUtils.isMatchingExecutable(Executable.of(method), parameterTypes);
|
||||
}
|
||||
|
||||
private static boolean isMatchingExecutable(final Executable method, final Class<?>[] parameterTypes) {
|
||||
final Class<?>[] methodParameterTypes = method.getParameterTypes();
|
||||
if (method.isVarArgs()) {
|
||||
int i;
|
||||
for (i = 0; i < methodParameterTypes.length - 1 && i < parameterTypes.length; i++) {
|
||||
if (!ClassUtils.isAssignable(parameterTypes[i], methodParameterTypes[i], true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final Class<?> varArgParameterType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
|
||||
for (; i < parameterTypes.length; i++) {
|
||||
if (!ClassUtils.isAssignable(parameterTypes[i], varArgParameterType, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return ClassUtils.isAssignable(parameterTypes, methodParameterTypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p> A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8,
|
||||
* providing a common representation for function signatures for Constructors and Methods.</p>
|
||||
*/
|
||||
private static final class Executable {
|
||||
private final Class<?>[] parameterTypes;
|
||||
private final boolean isVarArgs;
|
||||
|
||||
private static Executable of(final Method method) {
|
||||
return new Executable(method);
|
||||
}
|
||||
|
||||
private static Executable of(final Constructor<?> constructor) {
|
||||
return new Executable(constructor);
|
||||
}
|
||||
|
||||
private Executable(final Method method) {
|
||||
parameterTypes = method.getParameterTypes();
|
||||
isVarArgs = method.isVarArgs();
|
||||
}
|
||||
|
||||
private Executable(final Constructor<?> constructor) {
|
||||
parameterTypes = constructor.getParameterTypes();
|
||||
isVarArgs = constructor.isVarArgs();
|
||||
}
|
||||
|
||||
public Class<?>[] getParameterTypes() {
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return isVarArgs;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.reflect;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>Type literal comparable to {@code javax.enterprise.util.TypeLiteral},
|
||||
* made generally available outside the JEE context. Allows the passing around of
|
||||
* a "token" that represents a type in a typesafe manner, as opposed to
|
||||
* passing the (non-parameterized) {@link Type} object itself. Consider:</p>
|
||||
* <p>
|
||||
* You might see such a typesafe API as:
|
||||
* <pre>
|
||||
* class Typesafe {
|
||||
* <T> T obtain(Class<T> type, ...);
|
||||
* }
|
||||
* </pre>
|
||||
* Consumed in the manner of:
|
||||
* <pre>
|
||||
* Foo foo = typesafe.obtain(Foo.class, ...);
|
||||
* </pre>
|
||||
* Yet, you run into problems when you want to do this with a parameterized type:
|
||||
* <pre>
|
||||
* List<String> listOfString = typesafe.obtain(List.class, ...); // could only give us a raw List
|
||||
* </pre>
|
||||
* {@code java.lang.reflect.Type} might provide some value:
|
||||
* <pre>
|
||||
* Type listOfStringType = ...; // firstly, how to obtain this? Doable, but not straightforward.
|
||||
* List<String> listOfString = (List<String>) typesafe.obtain(listOfStringType, ...); // nongeneric Type would necessitate a cast
|
||||
* </pre>
|
||||
* The "type literal" concept was introduced to provide an alternative, i.e.:
|
||||
* <pre>
|
||||
* class Typesafe {
|
||||
* <T> T obtain(TypeLiteral<T> type, ...);
|
||||
* }
|
||||
* </pre>
|
||||
* Consuming code looks like:
|
||||
* <pre>
|
||||
* List<String> listOfString = typesafe.obtain(new TypeLiteral<List<String>>() {}, ...);
|
||||
* </pre>
|
||||
* <p>
|
||||
* This has the effect of "jumping up" a level to tie a {@code java.lang.reflect.Type}
|
||||
* to a type variable while simultaneously making it short work to obtain a
|
||||
* {@code Type} instance for any given type, inline.
|
||||
* </p>
|
||||
* <p>Additionally {@link TypeLiteral} implements the {@link Typed} interface which
|
||||
* is a generalization of this concept, and which may be implemented in custom classes.
|
||||
* It is suggested that APIs be defined in terms of the interface, in the following manner:
|
||||
* </p>
|
||||
* <pre>
|
||||
* <T> T obtain(Typed<T> typed, ...);
|
||||
* </pre>
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class TypeLiteral<T> implements Typed<T> {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final TypeVariable<Class<TypeLiteral>> T = TypeLiteral.class.getTypeParameters()[0];
|
||||
|
||||
/**
|
||||
* Represented type.
|
||||
*/
|
||||
public final Type value;
|
||||
|
||||
private final String toString;
|
||||
|
||||
/**
|
||||
* The default constructor.
|
||||
*/
|
||||
protected TypeLiteral() {
|
||||
this.value =
|
||||
Validate.notNull(TypeUtils.getTypeArguments(getClass(), TypeLiteral.class).get(T),
|
||||
"%s does not assign type parameter %s", getClass(), TypeUtils.toLongString(T));
|
||||
|
||||
this.toString = String.format("%s<%s>", TypeLiteral.class.getSimpleName(), TypeUtils.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof TypeLiteral == false) {
|
||||
return false;
|
||||
}
|
||||
final TypeLiteral<?> other = (TypeLiteral<?>) obj;
|
||||
return TypeUtils.equals(value, other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 37 << 4 | value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return value;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.reflect;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Generalization of "has a type."
|
||||
* @see TypeLiteral
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface Typed<T> {
|
||||
|
||||
/**
|
||||
* Get the {@link Type} represented by this entity.
|
||||
*
|
||||
* @return Type
|
||||
*/
|
||||
Type getType();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,445 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.text;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* A matcher class that can be queried to determine if a character array
|
||||
* portion matches.
|
||||
* <p>
|
||||
* This class comes complete with various factory methods.
|
||||
* If these do not suffice, you can subclass and implement your own matcher.
|
||||
*
|
||||
* @since 2.2
|
||||
* @deprecated as of 3.6, use commons-text
|
||||
* <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StrMatcher.html">
|
||||
* StrMatcher</a> instead
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class StrMatcher {
|
||||
|
||||
/**
|
||||
* Matches the comma character.
|
||||
*/
|
||||
private static final StrMatcher COMMA_MATCHER = new CharMatcher(',');
|
||||
/**
|
||||
* Matches the tab character.
|
||||
*/
|
||||
private static final StrMatcher TAB_MATCHER = new CharMatcher('\t');
|
||||
/**
|
||||
* Matches the space character.
|
||||
*/
|
||||
private static final StrMatcher SPACE_MATCHER = new CharMatcher(' ');
|
||||
/**
|
||||
* Matches the same characters as StringTokenizer,
|
||||
* namely space, tab, newline, formfeed.
|
||||
*/
|
||||
private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray());
|
||||
/**
|
||||
* Matches the String trim() whitespace characters.
|
||||
*/
|
||||
private static final StrMatcher TRIM_MATCHER = new TrimMatcher();
|
||||
/**
|
||||
* Matches the double quote character.
|
||||
*/
|
||||
private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\'');
|
||||
/**
|
||||
* Matches the double quote character.
|
||||
*/
|
||||
private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"');
|
||||
/**
|
||||
* Matches the single or double quote character.
|
||||
*/
|
||||
private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray());
|
||||
/**
|
||||
* Matches no characters.
|
||||
*/
|
||||
private static final StrMatcher NONE_MATCHER = new NoMatcher();
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a matcher which matches the comma character.
|
||||
*
|
||||
* @return a matcher for a comma
|
||||
*/
|
||||
public static StrMatcher commaMatcher() {
|
||||
return COMMA_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher which matches the tab character.
|
||||
*
|
||||
* @return a matcher for a tab
|
||||
*/
|
||||
public static StrMatcher tabMatcher() {
|
||||
return TAB_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher which matches the space character.
|
||||
*
|
||||
* @return a matcher for a space
|
||||
*/
|
||||
public static StrMatcher spaceMatcher() {
|
||||
return SPACE_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the same characters as StringTokenizer,
|
||||
* namely space, tab, newline and formfeed.
|
||||
*
|
||||
* @return the split matcher
|
||||
*/
|
||||
public static StrMatcher splitMatcher() {
|
||||
return SPLIT_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the String trim() whitespace characters.
|
||||
*
|
||||
* @return the trim matcher
|
||||
*/
|
||||
public static StrMatcher trimMatcher() {
|
||||
return TRIM_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher which matches the single quote character.
|
||||
*
|
||||
* @return a matcher for a single quote
|
||||
*/
|
||||
public static StrMatcher singleQuoteMatcher() {
|
||||
return SINGLE_QUOTE_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher which matches the double quote character.
|
||||
*
|
||||
* @return a matcher for a double quote
|
||||
*/
|
||||
public static StrMatcher doubleQuoteMatcher() {
|
||||
return DOUBLE_QUOTE_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher which matches the single or double quote character.
|
||||
*
|
||||
* @return a matcher for a single or double quote
|
||||
*/
|
||||
public static StrMatcher quoteMatcher() {
|
||||
return QUOTE_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches no characters.
|
||||
*
|
||||
* @return a matcher that matches nothing
|
||||
*/
|
||||
public static StrMatcher noneMatcher() {
|
||||
return NONE_MATCHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher from a character.
|
||||
*
|
||||
* @param ch the character to match, must not be null
|
||||
* @return a new Matcher for the given char
|
||||
*/
|
||||
public static StrMatcher charMatcher(final char ch) {
|
||||
return new CharMatcher(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher from a set of characters.
|
||||
*
|
||||
* @param chars the characters to match, null or empty matches nothing
|
||||
* @return a new matcher for the given char[]
|
||||
*/
|
||||
public static StrMatcher charSetMatcher(final char... chars) {
|
||||
if (chars == null || chars.length == 0) {
|
||||
return NONE_MATCHER;
|
||||
}
|
||||
if (chars.length == 1) {
|
||||
return new CharMatcher(chars[0]);
|
||||
}
|
||||
return new CharSetMatcher(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher from a string representing a set of characters.
|
||||
*
|
||||
* @param chars the characters to match, null or empty matches nothing
|
||||
* @return a new Matcher for the given characters
|
||||
*/
|
||||
public static StrMatcher charSetMatcher(final String chars) {
|
||||
if (StringUtils.isEmpty(chars)) {
|
||||
return NONE_MATCHER;
|
||||
}
|
||||
if (chars.length() == 1) {
|
||||
return new CharMatcher(chars.charAt(0));
|
||||
}
|
||||
return new CharSetMatcher(chars.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher from a string.
|
||||
*
|
||||
* @param str the string to match, null or empty matches nothing
|
||||
* @return a new Matcher for the given String
|
||||
*/
|
||||
public static StrMatcher stringMatcher(final String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return NONE_MATCHER;
|
||||
}
|
||||
return new StringMatcher(str);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected StrMatcher() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of matching characters, zero for no match.
|
||||
* <p>
|
||||
* This method is called to check for a match.
|
||||
* The parameter <code>pos</code> represents the current position to be
|
||||
* checked in the string <code>buffer</code> (a character array which must
|
||||
* not be changed).
|
||||
* The API guarantees that <code>pos</code> is a valid index for <code>buffer</code>.
|
||||
* <p>
|
||||
* The character array may be larger than the active area to be matched.
|
||||
* Only values in the buffer between the specified indices may be accessed.
|
||||
* <p>
|
||||
* The matching code may check one character or many.
|
||||
* It may check characters preceding <code>pos</code> as well as those
|
||||
* after, so long as no checks exceed the bounds specified.
|
||||
* <p>
|
||||
* It must return zero for no match, or a positive number if a match was found.
|
||||
* The number indicates the number of characters that matched.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @param bufferStart the first active index in the buffer, valid for buffer
|
||||
* @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
*/
|
||||
public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
|
||||
|
||||
/**
|
||||
* Returns the number of matching characters, zero for no match.
|
||||
* <p>
|
||||
* This method is called to check for a match.
|
||||
* The parameter <code>pos</code> represents the current position to be
|
||||
* checked in the string <code>buffer</code> (a character array which must
|
||||
* not be changed).
|
||||
* The API guarantees that <code>pos</code> is a valid index for <code>buffer</code>.
|
||||
* <p>
|
||||
* The matching code may check one character or many.
|
||||
* It may check characters preceding <code>pos</code> as well as those after.
|
||||
* <p>
|
||||
* It must return zero for no match, or a positive number if a match was found.
|
||||
* The number indicates the number of characters that matched.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
* @since 2.4
|
||||
*/
|
||||
public int isMatch(final char[] buffer, final int pos) {
|
||||
return isMatch(buffer, pos, 0, buffer.length);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Class used to define a set of characters for matching purposes.
|
||||
*/
|
||||
static final class CharSetMatcher extends StrMatcher {
|
||||
/** The set of characters to match. */
|
||||
private final char[] chars;
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher from a character array.
|
||||
*
|
||||
* @param chars the characters to match, must not be null
|
||||
*/
|
||||
CharSetMatcher(final char chars[]) {
|
||||
super();
|
||||
this.chars = chars.clone();
|
||||
Arrays.sort(this.chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given character matches.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @param bufferStart the first active index in the buffer, valid for buffer
|
||||
* @param bufferEnd the end index of the active buffer, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
*/
|
||||
@Override
|
||||
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
|
||||
return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Class used to define a character for matching purposes.
|
||||
*/
|
||||
static final class CharMatcher extends StrMatcher {
|
||||
/** The character to match. */
|
||||
private final char ch;
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher that matches a single character.
|
||||
*
|
||||
* @param ch the character to match
|
||||
*/
|
||||
CharMatcher(final char ch) {
|
||||
super();
|
||||
this.ch = ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given character matches.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @param bufferStart the first active index in the buffer, valid for buffer
|
||||
* @param bufferEnd the end index of the active buffer, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
*/
|
||||
@Override
|
||||
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
|
||||
return ch == buffer[pos] ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Class used to define a set of characters for matching purposes.
|
||||
*/
|
||||
static final class StringMatcher extends StrMatcher {
|
||||
/** The string to match, as a character array. */
|
||||
private final char[] chars;
|
||||
|
||||
/**
|
||||
* Constructor that creates a matcher from a String.
|
||||
*
|
||||
* @param str the string to match, must not be null
|
||||
*/
|
||||
StringMatcher(final String str) {
|
||||
super();
|
||||
chars = str.toCharArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given text matches the stored string.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @param bufferStart the first active index in the buffer, valid for buffer
|
||||
* @param bufferEnd the end index of the active buffer, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
*/
|
||||
@Override
|
||||
public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
|
||||
final int len = chars.length;
|
||||
if (pos + len > bufferEnd) {
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < chars.length; i++, pos++) {
|
||||
if (chars[i] != buffer[pos]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + Arrays.toString(chars);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Class used to match no characters.
|
||||
*/
|
||||
static final class NoMatcher extends StrMatcher {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of <code>NoMatcher</code>.
|
||||
*/
|
||||
NoMatcher() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns <code>false</code>.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @param bufferStart the first active index in the buffer, valid for buffer
|
||||
* @param bufferEnd the end index of the active buffer, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
*/
|
||||
@Override
|
||||
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Class used to match whitespace as per trim().
|
||||
*/
|
||||
static final class TrimMatcher extends StrMatcher {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of <code>TrimMatcher</code>.
|
||||
*/
|
||||
TrimMatcher() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given character matches.
|
||||
*
|
||||
* @param buffer the text content to match against, do not change
|
||||
* @param pos the starting position for the match, valid for buffer
|
||||
* @param bufferStart the first active index in the buffer, valid for buffer
|
||||
* @param bufferEnd the end index of the active buffer, valid for buffer
|
||||
* @return the number of matching characters, zero for no match
|
||||
*/
|
||||
@Override
|
||||
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
|
||||
return buffer[pos] <= 32 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,739 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.text;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ArrayUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.StringUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>Operations on Strings that contain words.</p>
|
||||
*
|
||||
* <p>This class tries to handle <code>null</code> input gracefully.
|
||||
* An exception will not be thrown for a <code>null</code> input.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* @since 2.0
|
||||
* @deprecated as of 3.6, use commons-text
|
||||
* <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/WordUtils.html">
|
||||
* WordUtils</a> instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class WordUtils {
|
||||
|
||||
/**
|
||||
* <p><code>WordUtils</code> instances should NOT be constructed in
|
||||
* standard programming. Instead, the class should be used as
|
||||
* <code>WordUtils.wrap("foo bar", 20);</code>.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public WordUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
// Wrapping
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
|
||||
*
|
||||
* <p>New lines will be separated by the system property line separator.
|
||||
* Very long words, such as URLs will <i>not</i> be wrapped.</p>
|
||||
*
|
||||
* <p>Leading spaces on a new line are stripped.
|
||||
* Trailing spaces are not stripped.</p>
|
||||
*
|
||||
* <table border="1" summary="Wrap Results">
|
||||
* <tr>
|
||||
* <th>input</th>
|
||||
* <th>wrapLength</th>
|
||||
* <th>result</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>null</td>
|
||||
* <td>*</td>
|
||||
* <td>null</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>""</td>
|
||||
* <td>*</td>
|
||||
* <td>""</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
|
||||
* <td>20</td>
|
||||
* <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Click here, http://commons.apache.org, to jump to the commons website"</td>
|
||||
* <td>20</td>
|
||||
* <td>"Click here,\nhttp://commons.apache.org,\nto jump to the\ncommons website"</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* (assuming that '\n' is the systems line separator)
|
||||
*
|
||||
* @param str the String to be word wrapped, may be null
|
||||
* @param wrapLength the column to wrap the words at, less than 1 is treated as 1
|
||||
* @return a line with newlines inserted, <code>null</code> if null input
|
||||
*/
|
||||
public static String wrap(final String str, final int wrapLength) {
|
||||
return wrap(str, wrapLength, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
|
||||
*
|
||||
* <p>Leading spaces on a new line are stripped.
|
||||
* Trailing spaces are not stripped.</p>
|
||||
*
|
||||
* <table border="1" summary="Wrap Results">
|
||||
* <tr>
|
||||
* <th>input</th>
|
||||
* <th>wrapLength</th>
|
||||
* <th>newLineString</th>
|
||||
* <th>wrapLongWords</th>
|
||||
* <th>result</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>null</td>
|
||||
* <td>*</td>
|
||||
* <td>*</td>
|
||||
* <td>true/false</td>
|
||||
* <td>null</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>""</td>
|
||||
* <td>*</td>
|
||||
* <td>*</td>
|
||||
* <td>true/false</td>
|
||||
* <td>""</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>true/false</td>
|
||||
* <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>"<br />"</td>
|
||||
* <td>true/false</td>
|
||||
* <td>"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>null</td>
|
||||
* <td>true/false</td>
|
||||
* <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>false</td>
|
||||
* <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>true</td>
|
||||
* <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @param str the String to be word wrapped, may be null
|
||||
* @param wrapLength the column to wrap the words at, less than 1 is treated as 1
|
||||
* @param newLineStr the string to insert for a new line,
|
||||
* <code>null</code> uses the system property line separator
|
||||
* @param wrapLongWords true if long words (such as URLs) should be wrapped
|
||||
* @return a line with newlines inserted, <code>null</code> if null input
|
||||
*/
|
||||
public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) {
|
||||
return wrap(str, wrapLength, newLineStr, wrapLongWords, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Wraps a single line of text, identifying words by <code>wrapOn</code>.</p>
|
||||
*
|
||||
* <p>Leading spaces on a new line are stripped.
|
||||
* Trailing spaces are not stripped.</p>
|
||||
*
|
||||
* <table border="1" summary="Wrap Results">
|
||||
* <tr>
|
||||
* <th>input</th>
|
||||
* <th>wrapLength</th>
|
||||
* <th>newLineString</th>
|
||||
* <th>wrapLongWords</th>
|
||||
* <th>wrapOn</th>
|
||||
* <th>result</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>null</td>
|
||||
* <td>*</td>
|
||||
* <td>*</td>
|
||||
* <td>true/false</td>
|
||||
* <td>*</td>
|
||||
* <td>null</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>""</td>
|
||||
* <td>*</td>
|
||||
* <td>*</td>
|
||||
* <td>true/false</td>
|
||||
* <td>*</td>
|
||||
* <td>""</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>true/false</td>
|
||||
* <td>" "</td>
|
||||
* <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>"<br />"</td>
|
||||
* <td>true/false</td>
|
||||
* <td>" "</td>
|
||||
* <td>"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
|
||||
* <td>20</td>
|
||||
* <td>null</td>
|
||||
* <td>true/false</td>
|
||||
* <td>" "</td>
|
||||
* <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>false</td>
|
||||
* <td>" "</td>
|
||||
* <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>true</td>
|
||||
* <td>" "</td>
|
||||
* <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>"flammable/inflammable"</td>
|
||||
* <td>20</td>
|
||||
* <td>"\n"</td>
|
||||
* <td>true</td>
|
||||
* <td>"/"</td>
|
||||
* <td>"flammable\ninflammable"</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* @param str the String to be word wrapped, may be null
|
||||
* @param wrapLength the column to wrap the words at, less than 1 is treated as 1
|
||||
* @param newLineStr the string to insert for a new line,
|
||||
* <code>null</code> uses the system property line separator
|
||||
* @param wrapLongWords true if long words (such as URLs) should be wrapped
|
||||
* @param wrapOn regex expression to be used as a breakable characters,
|
||||
* if blank string is provided a space character will be used
|
||||
* @return a line with newlines inserted, <code>null</code> if null input
|
||||
*/
|
||||
public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
if (newLineStr == null) {
|
||||
newLineStr = System.lineSeparator();
|
||||
}
|
||||
if (wrapLength < 1) {
|
||||
wrapLength = 1;
|
||||
}
|
||||
if (StringUtils.isBlank(wrapOn)) {
|
||||
wrapOn = " ";
|
||||
}
|
||||
final Pattern patternToWrapOn = Pattern.compile(wrapOn);
|
||||
final int inputLineLength = str.length();
|
||||
int offset = 0;
|
||||
final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
|
||||
|
||||
while (offset < inputLineLength) {
|
||||
int spaceToWrapAt = -1;
|
||||
Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset + wrapLength + 1, inputLineLength)));
|
||||
if (matcher.find()) {
|
||||
if (matcher.start() == 0) {
|
||||
offset += matcher.end();
|
||||
continue;
|
||||
}
|
||||
spaceToWrapAt = matcher.start() + offset;
|
||||
}
|
||||
|
||||
// only last line without leading spaces is left
|
||||
if(inputLineLength - offset <= wrapLength) {
|
||||
break;
|
||||
}
|
||||
|
||||
while(matcher.find()){
|
||||
spaceToWrapAt = matcher.start() + offset;
|
||||
}
|
||||
|
||||
if (spaceToWrapAt >= offset) {
|
||||
// normal case
|
||||
wrappedLine.append(str.substring(offset, spaceToWrapAt));
|
||||
wrappedLine.append(newLineStr);
|
||||
offset = spaceToWrapAt + 1;
|
||||
|
||||
} else {
|
||||
// really long word or URL
|
||||
if (wrapLongWords) {
|
||||
// wrap really long word one line at a time
|
||||
wrappedLine.append(str.substring(offset, wrapLength + offset));
|
||||
wrappedLine.append(newLineStr);
|
||||
offset += wrapLength;
|
||||
} else {
|
||||
// do not wrap really long word, just extend beyond limit
|
||||
matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
|
||||
if (matcher.find()) {
|
||||
spaceToWrapAt = matcher.start() + offset + wrapLength;
|
||||
}
|
||||
|
||||
if (spaceToWrapAt >= 0) {
|
||||
wrappedLine.append(str.substring(offset, spaceToWrapAt));
|
||||
wrappedLine.append(newLineStr);
|
||||
offset = spaceToWrapAt + 1;
|
||||
} else {
|
||||
wrappedLine.append(str.substring(offset));
|
||||
offset = inputLineLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever is left in line is short enough to just pass through
|
||||
wrappedLine.append(str.substring(offset));
|
||||
|
||||
return wrappedLine.toString();
|
||||
}
|
||||
|
||||
// Capitalizing
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Capitalizes all the whitespace separated words in a String.
|
||||
* Only the first character of each word is changed. To convert the
|
||||
* rest of each word to lowercase at the same time,
|
||||
* use {@link #capitalizeFully(String)}.</p>
|
||||
*
|
||||
* <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.
|
||||
* Capitalization uses the Unicode title case, normally equivalent to
|
||||
* upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.capitalize(null) = null
|
||||
* WordUtils.capitalize("") = ""
|
||||
* WordUtils.capitalize("i am FINE") = "I Am FINE"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to capitalize, may be null
|
||||
* @return capitalized String, <code>null</code> if null String input
|
||||
* @see #uncapitalize(String)
|
||||
* @see #capitalizeFully(String)
|
||||
*/
|
||||
public static String capitalize(final String str) {
|
||||
return capitalize(str, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Capitalizes all the delimiter separated words in a String.
|
||||
* Only the first character of each word is changed. To convert the
|
||||
* rest of each word to lowercase at the same time,
|
||||
* use {@link #capitalizeFully(String, char[])}.</p>
|
||||
*
|
||||
* <p>The delimiters represent a set of characters understood to separate words.
|
||||
* The first string character and the first non-delimiter character after a
|
||||
* delimiter will be capitalized. </p>
|
||||
*
|
||||
* <p>A <code>null</code> input String returns <code>null</code>.
|
||||
* Capitalization uses the Unicode title case, normally equivalent to
|
||||
* upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.capitalize(null, *) = null
|
||||
* WordUtils.capitalize("", *) = ""
|
||||
* WordUtils.capitalize(*, new char[0]) = *
|
||||
* WordUtils.capitalize("i am fine", null) = "I Am Fine"
|
||||
* WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to capitalize, may be null
|
||||
* @param delimiters set of characters to determine capitalization, null means whitespace
|
||||
* @return capitalized String, <code>null</code> if null String input
|
||||
* @see #uncapitalize(String)
|
||||
* @see #capitalizeFully(String)
|
||||
* @since 2.1
|
||||
*/
|
||||
public static String capitalize(final String str, final char... delimiters) {
|
||||
final int delimLen = delimiters == null ? -1 : delimiters.length;
|
||||
if (StringUtils.isEmpty(str) || delimLen == 0) {
|
||||
return str;
|
||||
}
|
||||
final char[] buffer = str.toCharArray();
|
||||
boolean capitalizeNext = true;
|
||||
for (int i = 0; i < buffer.length; i++) {
|
||||
final char ch = buffer[i];
|
||||
if (isDelimiter(ch, delimiters)) {
|
||||
capitalizeNext = true;
|
||||
} else if (capitalizeNext) {
|
||||
buffer[i] = Character.toTitleCase(ch);
|
||||
capitalizeNext = false;
|
||||
}
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts all the whitespace separated words in a String into capitalized words,
|
||||
* that is each word is made up of a titlecase character and then a series of
|
||||
* lowercase characters. </p>
|
||||
*
|
||||
* <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.
|
||||
* Capitalization uses the Unicode title case, normally equivalent to
|
||||
* upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.capitalizeFully(null) = null
|
||||
* WordUtils.capitalizeFully("") = ""
|
||||
* WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to capitalize, may be null
|
||||
* @return capitalized String, <code>null</code> if null String input
|
||||
*/
|
||||
public static String capitalizeFully(final String str) {
|
||||
return capitalizeFully(str, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts all the delimiter separated words in a String into capitalized words,
|
||||
* that is each word is made up of a titlecase character and then a series of
|
||||
* lowercase characters. </p>
|
||||
*
|
||||
* <p>The delimiters represent a set of characters understood to separate words.
|
||||
* The first string character and the first non-delimiter character after a
|
||||
* delimiter will be capitalized. </p>
|
||||
*
|
||||
* <p>A <code>null</code> input String returns <code>null</code>.
|
||||
* Capitalization uses the Unicode title case, normally equivalent to
|
||||
* upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.capitalizeFully(null, *) = null
|
||||
* WordUtils.capitalizeFully("", *) = ""
|
||||
* WordUtils.capitalizeFully(*, null) = *
|
||||
* WordUtils.capitalizeFully(*, new char[0]) = *
|
||||
* WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to capitalize, may be null
|
||||
* @param delimiters set of characters to determine capitalization, null means whitespace
|
||||
* @return capitalized String, <code>null</code> if null String input
|
||||
* @since 2.1
|
||||
*/
|
||||
public static String capitalizeFully(String str, final char... delimiters) {
|
||||
final int delimLen = delimiters == null ? -1 : delimiters.length;
|
||||
if (StringUtils.isEmpty(str) || delimLen == 0) {
|
||||
return str;
|
||||
}
|
||||
str = str.toLowerCase();
|
||||
return capitalize(str, delimiters);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Uncapitalizes all the whitespace separated words in a String.
|
||||
* Only the first character of each word is changed.</p>
|
||||
*
|
||||
* <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.uncapitalize(null) = null
|
||||
* WordUtils.uncapitalize("") = ""
|
||||
* WordUtils.uncapitalize("I Am FINE") = "i am fINE"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to uncapitalize, may be null
|
||||
* @return uncapitalized String, <code>null</code> if null String input
|
||||
* @see #capitalize(String)
|
||||
*/
|
||||
public static String uncapitalize(final String str) {
|
||||
return uncapitalize(str, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Uncapitalizes all the whitespace separated words in a String.
|
||||
* Only the first character of each word is changed.</p>
|
||||
*
|
||||
* <p>The delimiters represent a set of characters understood to separate words.
|
||||
* The first string character and the first non-delimiter character after a
|
||||
* delimiter will be uncapitalized. </p>
|
||||
*
|
||||
* <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.uncapitalize(null, *) = null
|
||||
* WordUtils.uncapitalize("", *) = ""
|
||||
* WordUtils.uncapitalize(*, null) = *
|
||||
* WordUtils.uncapitalize(*, new char[0]) = *
|
||||
* WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to uncapitalize, may be null
|
||||
* @param delimiters set of characters to determine uncapitalization, null means whitespace
|
||||
* @return uncapitalized String, <code>null</code> if null String input
|
||||
* @see #capitalize(String)
|
||||
* @since 2.1
|
||||
*/
|
||||
public static String uncapitalize(final String str, final char... delimiters) {
|
||||
final int delimLen = delimiters == null ? -1 : delimiters.length;
|
||||
if (StringUtils.isEmpty(str) || delimLen == 0) {
|
||||
return str;
|
||||
}
|
||||
final char[] buffer = str.toCharArray();
|
||||
boolean uncapitalizeNext = true;
|
||||
for (int i = 0; i < buffer.length; i++) {
|
||||
final char ch = buffer[i];
|
||||
if (isDelimiter(ch, delimiters)) {
|
||||
uncapitalizeNext = true;
|
||||
} else if (uncapitalizeNext) {
|
||||
buffer[i] = Character.toLowerCase(ch);
|
||||
uncapitalizeNext = false;
|
||||
}
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Swaps the case of a String using a word based algorithm.</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Upper case character converts to Lower case</li>
|
||||
* <li>Title case character converts to Lower case</li>
|
||||
* <li>Lower case character after Whitespace or at start converts to Title case</li>
|
||||
* <li>Other Lower case character converts to Upper case</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.</p>
|
||||
*
|
||||
* <pre>
|
||||
* StringUtils.swapCase(null) = null
|
||||
* StringUtils.swapCase("") = ""
|
||||
* StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to swap case, may be null
|
||||
* @return the changed String, <code>null</code> if null String input
|
||||
*/
|
||||
public static String swapCase(final String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return str;
|
||||
}
|
||||
final char[] buffer = str.toCharArray();
|
||||
|
||||
boolean whitespace = true;
|
||||
|
||||
for (int i = 0; i < buffer.length; i++) {
|
||||
final char ch = buffer[i];
|
||||
if (Character.isUpperCase(ch)) {
|
||||
buffer[i] = Character.toLowerCase(ch);
|
||||
whitespace = false;
|
||||
} else if (Character.isTitleCase(ch)) {
|
||||
buffer[i] = Character.toLowerCase(ch);
|
||||
whitespace = false;
|
||||
} else if (Character.isLowerCase(ch)) {
|
||||
if (whitespace) {
|
||||
buffer[i] = Character.toTitleCase(ch);
|
||||
whitespace = false;
|
||||
} else {
|
||||
buffer[i] = Character.toUpperCase(ch);
|
||||
}
|
||||
} else {
|
||||
whitespace = Character.isWhitespace(ch);
|
||||
}
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Extracts the initial characters from each word in the String.</p>
|
||||
*
|
||||
* <p>All first characters after whitespace are returned as a new string.
|
||||
* Their case is not changed.</p>
|
||||
*
|
||||
* <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.initials(null) = null
|
||||
* WordUtils.initials("") = ""
|
||||
* WordUtils.initials("Ben John Lee") = "BJL"
|
||||
* WordUtils.initials("Ben J.Lee") = "BJ"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to get initials from, may be null
|
||||
* @return String of initial letters, <code>null</code> if null String input
|
||||
* @see #initials(String,char[])
|
||||
* @since 2.2
|
||||
*/
|
||||
public static String initials(final String str) {
|
||||
return initials(str, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Extracts the initial characters from each word in the String.</p>
|
||||
*
|
||||
* <p>All first characters after the defined delimiters are returned as a new string.
|
||||
* Their case is not changed.</p>
|
||||
*
|
||||
* <p>If the delimiters array is null, then Whitespace is used.
|
||||
* Whitespace is defined by {@link Character#isWhitespace(char)}.
|
||||
* A <code>null</code> input String returns <code>null</code>.
|
||||
* An empty delimiter array returns an empty String.</p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.initials(null, *) = null
|
||||
* WordUtils.initials("", *) = ""
|
||||
* WordUtils.initials("Ben John Lee", null) = "BJL"
|
||||
* WordUtils.initials("Ben J.Lee", null) = "BJ"
|
||||
* WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
|
||||
* WordUtils.initials(*, new char[0]) = ""
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to get initials from, may be null
|
||||
* @param delimiters set of characters to determine words, null means whitespace
|
||||
* @return String of initial characters, <code>null</code> if null String input
|
||||
* @see #initials(String)
|
||||
* @since 2.2
|
||||
*/
|
||||
public static String initials(final String str, final char... delimiters) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return str;
|
||||
}
|
||||
if (delimiters != null && delimiters.length == 0) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
final int strLen = str.length();
|
||||
final char[] buf = new char[strLen / 2 + 1];
|
||||
int count = 0;
|
||||
boolean lastWasGap = true;
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
final char ch = str.charAt(i);
|
||||
|
||||
if (isDelimiter(ch, delimiters)) {
|
||||
lastWasGap = true;
|
||||
} else if (lastWasGap) {
|
||||
buf[count++] = ch;
|
||||
lastWasGap = false;
|
||||
} else {
|
||||
continue; // ignore ch
|
||||
}
|
||||
}
|
||||
return new String(buf, 0, count);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Checks if the String contains all words in the given array.</p>
|
||||
*
|
||||
* <p>
|
||||
* A {@code null} String will return {@code false}. A {@code null}, zero
|
||||
* length search array or if one element of array is null will return {@code false}.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* WordUtils.containsAllWords(null, *) = false
|
||||
* WordUtils.containsAllWords("", *) = false
|
||||
* WordUtils.containsAllWords(*, null) = false
|
||||
* WordUtils.containsAllWords(*, []) = false
|
||||
* WordUtils.containsAllWords("abcd", "ab", "cd") = false
|
||||
* WordUtils.containsAllWords("abc def", "def", "abc") = true
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @param word The CharSequence to check, may be null
|
||||
* @param words The array of String words to search for, may be null
|
||||
* @return {@code true} if all search words are found, {@code false} otherwise
|
||||
* @since 3.5
|
||||
*/
|
||||
public static boolean containsAllWords(final CharSequence word, final CharSequence... words) {
|
||||
if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) {
|
||||
return false;
|
||||
}
|
||||
for (final CharSequence w : words) {
|
||||
if (StringUtils.isBlank(w)) {
|
||||
return false;
|
||||
}
|
||||
final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*");
|
||||
if (!p.matcher(word).matches()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Is the character a delimiter.
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @param delimiters the delimiters
|
||||
* @return true if it is a delimiter
|
||||
*/
|
||||
private static boolean isDelimiter(final char ch, final char[] delimiters) {
|
||||
if (delimiters == null) {
|
||||
return Character.isWhitespace(ch);
|
||||
}
|
||||
for (final char delimiter : delimiters) {
|
||||
if (ch == delimiter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.time;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* DateParser is the "missing" interface for the parsing methods of
|
||||
* {@link java.text.DateFormat}. You can obtain an object implementing this
|
||||
* interface by using one of the FastDateFormat factory methods.
|
||||
* <p>
|
||||
* Warning: Since binary compatible methods may be added to this interface in any
|
||||
* release, developers are not expected to implement this interface.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface DateParser {
|
||||
|
||||
/**
|
||||
* Equivalent to DateFormat.parse(String).
|
||||
*
|
||||
* See {@link java.text.DateFormat#parse(String)} for more information.
|
||||
* @param source A <code>String</code> whose beginning should be parsed.
|
||||
* @return A <code>Date</code> parsed from the string
|
||||
* @throws ParseException if the beginning of the specified string cannot be parsed.
|
||||
*/
|
||||
Date parse(String source) throws ParseException;
|
||||
|
||||
/**
|
||||
* Equivalent to DateFormat.parse(String, ParsePosition).
|
||||
*
|
||||
* See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information.
|
||||
*
|
||||
* @param source A <code>String</code>, part of which should be parsed.
|
||||
* @param pos A <code>ParsePosition</code> object with index and error index information
|
||||
* as described above.
|
||||
* @return A <code>Date</code> parsed from the string. In case of error, returns null.
|
||||
* @throws NullPointerException if text or pos is null.
|
||||
*/
|
||||
Date parse(String source, ParsePosition pos);
|
||||
|
||||
/**
|
||||
* Parses a formatted date string according to the format. Updates the Calendar with parsed fields.
|
||||
* Upon success, the ParsePosition index is updated to indicate how much of the source text was consumed.
|
||||
* Not all source text needs to be consumed. Upon parse failure, ParsePosition error index is updated to
|
||||
* the offset of the source text which does not match the supplied format.
|
||||
*
|
||||
* @param source The text to parse.
|
||||
* @param pos On input, the position in the source to start parsing, on output, updated position.
|
||||
* @param calendar The calendar into which to set parsed fields.
|
||||
* @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated)
|
||||
* @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is
|
||||
* out of range.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
boolean parse(String source, ParsePosition pos, Calendar calendar);
|
||||
|
||||
// Accessors
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the pattern used by this parser.</p>
|
||||
*
|
||||
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
|
||||
*/
|
||||
String getPattern();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the time zone used by this parser.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by
|
||||
* the format pattern.
|
||||
* </p>
|
||||
*
|
||||
* @return the time zone
|
||||
*/
|
||||
TimeZone getTimeZone();
|
||||
|
||||
/**
|
||||
* <p>Gets the locale used by this parser.</p>
|
||||
*
|
||||
* @return the locale
|
||||
*/
|
||||
Locale getLocale();
|
||||
|
||||
/**
|
||||
* Parses text from a string to produce a Date.
|
||||
*
|
||||
* @param source A <code>String</code> whose beginning should be parsed.
|
||||
* @return a <code>java.util.Date</code> object
|
||||
* @throws ParseException if the beginning of the specified string cannot be parsed.
|
||||
* @see java.text.DateFormat#parseObject(String)
|
||||
*/
|
||||
Object parseObject(String source) throws ParseException;
|
||||
|
||||
/**
|
||||
* Parses a date/time string according to the given parse position.
|
||||
*
|
||||
* @param source A <code>String</code> whose beginning should be parsed.
|
||||
* @param pos the parse position
|
||||
* @return a <code>java.util.Date</code> object
|
||||
* @see java.text.DateFormat#parseObject(String, ParsePosition)
|
||||
*/
|
||||
Object parseObject(String source, ParsePosition pos);
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.time;
|
||||
|
||||
import java.text.FieldPosition;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* DatePrinter is the "missing" interface for the format methods of
|
||||
* {@link java.text.DateFormat}. You can obtain an object implementing this
|
||||
* interface by using one of the FastDateFormat factory methods.
|
||||
* <p>
|
||||
* Warning: Since binary compatible methods may be added to this interface in any
|
||||
* release, developers are not expected to implement this interface.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface DatePrinter {
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @return the formatted string
|
||||
* @since 2.1
|
||||
*/
|
||||
String format(long millis);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @return the formatted string
|
||||
*/
|
||||
String format(Date date);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object.</p>
|
||||
* The TimeZone set on the Calendar is only used to adjust the time offset.
|
||||
* The TimeZone specified during the construction of the Parser will determine the TimeZone
|
||||
* used in the formatted string.
|
||||
*
|
||||
* @param calendar the calendar to format.
|
||||
* @return the formatted string
|
||||
*/
|
||||
String format(Calendar calendar);
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @deprecated Use {{@link #format(long, Appendable)}.
|
||||
*/
|
||||
@Deprecated
|
||||
StringBuffer format(long millis, StringBuffer buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object into the
|
||||
* supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @deprecated Use {{@link #format(Date, Appendable)}.
|
||||
*/
|
||||
@Deprecated
|
||||
StringBuffer format(Date date, StringBuffer buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object into the supplied {@code StringBuffer}.</p>
|
||||
* The TimeZone set on the Calendar is only used to adjust the time offset.
|
||||
* The TimeZone specified during the construction of the Parser will determine the TimeZone
|
||||
* used in the formatted string.
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @deprecated Use {{@link #format(Calendar, Appendable)}.
|
||||
*/
|
||||
@Deprecated
|
||||
StringBuffer format(Calendar calendar, StringBuffer buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value into the
|
||||
* supplied {@code Appendable}.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @param buf the buffer to format into
|
||||
* @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
|
||||
* @return the specified string buffer
|
||||
* @since 3.5
|
||||
*/
|
||||
<B extends Appendable> B format(long millis, B buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object into the
|
||||
* supplied {@code Appendable} using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @param buf the buffer to format into
|
||||
* @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
|
||||
* @return the specified string buffer
|
||||
* @since 3.5
|
||||
*/
|
||||
<B extends Appendable> B format(Date date, B buf);
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object into the supplied {@code Appendable}.</p>
|
||||
* The TimeZone set on the Calendar is only used to adjust the time offset.
|
||||
* The TimeZone specified during the construction of the Parser will determine the TimeZone
|
||||
* used in the formatted string.
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @param buf the buffer to format into
|
||||
* @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
|
||||
* @return the specified string buffer
|
||||
* @since 3.5
|
||||
*/
|
||||
<B extends Appendable> B format(Calendar calendar, B buf);
|
||||
|
||||
|
||||
// Accessors
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the pattern used by this printer.</p>
|
||||
*
|
||||
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
|
||||
*/
|
||||
String getPattern();
|
||||
|
||||
/**
|
||||
* <p>Gets the time zone used by this printer.</p>
|
||||
*
|
||||
* <p>This zone is always used for {@code Date} printing. </p>
|
||||
*
|
||||
* @return the time zone
|
||||
*/
|
||||
TimeZone getTimeZone();
|
||||
|
||||
/**
|
||||
* <p>Gets the locale used by this printer.</p>
|
||||
*
|
||||
* @return the locale
|
||||
*/
|
||||
Locale getLocale();
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date}, {@code Calendar} or
|
||||
* {@code Long} (milliseconds) object.</p>
|
||||
*
|
||||
* @param obj the object to format
|
||||
* @param toAppendTo the buffer to append to
|
||||
* @param pos the position - ignored
|
||||
* @return the buffer passed in
|
||||
* @see java.text.DateFormat#format(Object, StringBuffer, FieldPosition)
|
||||
*/
|
||||
StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos);
|
||||
}
|
@ -1,676 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.time;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* <p>FastDateFormat is a fast and thread-safe version of
|
||||
* {@link java.text.SimpleDateFormat}.</p>
|
||||
*
|
||||
* <p>To obtain an instance of FastDateFormat, use one of the static factory methods:
|
||||
* {@link #getInstance(String, TimeZone, Locale)}, {@link #getDateInstance(int, TimeZone, Locale)},
|
||||
* {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)}
|
||||
* </p>
|
||||
*
|
||||
* <p>Since FastDateFormat is thread safe, you can use a static member instance:</p>
|
||||
* <code>
|
||||
* private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT);
|
||||
* </code>
|
||||
*
|
||||
* <p>This class can be used as a direct replacement to
|
||||
* {@code SimpleDateFormat} in most formatting and parsing situations.
|
||||
* This class is especially useful in multi-threaded server environments.
|
||||
* {@code SimpleDateFormat} is not thread-safe in any JDK version,
|
||||
* nor will it be as Sun have closed the bug/RFE.
|
||||
* </p>
|
||||
*
|
||||
* <p>All patterns are compatible with
|
||||
* SimpleDateFormat (except time zones and some year patterns - see below).</p>
|
||||
*
|
||||
* <p>Since 3.2, FastDateFormat supports parsing as well as printing.</p>
|
||||
*
|
||||
* <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
|
||||
* time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
|
||||
* This pattern letter can be used here (on all JDK versions).</p>
|
||||
*
|
||||
* <p>In addition, the pattern {@code 'ZZ'} has been made to represent
|
||||
* ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
|
||||
* This introduces a minor incompatibility with Java 1.4, but at a gain of
|
||||
* useful functionality.</p>
|
||||
*
|
||||
* <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
|
||||
* pattern letters is 2, the year is truncated to 2 digits; otherwise it is
|
||||
* interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
|
||||
* 'YYY' will be formatted as '2003', while it was '03' in former Java
|
||||
* versions. FastDateFormat implements the behavior of Java 7.</p>
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class FastDateFormat extends Format implements DateParser, DatePrinter {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 2L;
|
||||
|
||||
/**
|
||||
* FULL locale dependent date or time style.
|
||||
*/
|
||||
|
||||
public static final int FULL = DateFormat.FULL;
|
||||
|
||||
/**
|
||||
* LONG locale dependent date or time style.
|
||||
*/
|
||||
public static final int LONG = DateFormat.LONG;
|
||||
|
||||
/**
|
||||
* MEDIUM locale dependent date or time style.
|
||||
*/
|
||||
public static final int MEDIUM = DateFormat.MEDIUM;
|
||||
|
||||
/**
|
||||
* SHORT locale dependent date or time style.
|
||||
*/
|
||||
public static final int SHORT = DateFormat.SHORT;
|
||||
|
||||
private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
|
||||
@Override
|
||||
protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
|
||||
return new FastDateFormat(pattern, timeZone, locale);
|
||||
}
|
||||
};
|
||||
|
||||
private final FastDatePrinter printer;
|
||||
private final FastDateParser parser;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets a formatter instance using the default pattern in the
|
||||
* default locale.</p>
|
||||
*
|
||||
* @return a date/time formatter
|
||||
*/
|
||||
public static FastDateFormat getInstance() {
|
||||
return cache.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a formatter instance using the specified pattern in the
|
||||
* default locale.</p>
|
||||
*
|
||||
* @param pattern {@link java.text.SimpleDateFormat} compatible
|
||||
* pattern
|
||||
* @return a pattern based date/time formatter
|
||||
* @throws IllegalArgumentException if pattern is invalid
|
||||
*/
|
||||
public static FastDateFormat getInstance(final String pattern) {
|
||||
return cache.getInstance(pattern, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a formatter instance using the specified pattern and
|
||||
* time zone.</p>
|
||||
*
|
||||
* @param pattern {@link java.text.SimpleDateFormat} compatible
|
||||
* pattern
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date
|
||||
* @return a pattern based date/time formatter
|
||||
* @throws IllegalArgumentException if pattern is invalid
|
||||
*/
|
||||
public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
|
||||
return cache.getInstance(pattern, timeZone, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a formatter instance using the specified pattern and
|
||||
* locale.</p>
|
||||
*
|
||||
* @param pattern {@link java.text.SimpleDateFormat} compatible
|
||||
* pattern
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a pattern based date/time formatter
|
||||
* @throws IllegalArgumentException if pattern is invalid
|
||||
*/
|
||||
public static FastDateFormat getInstance(final String pattern, final Locale locale) {
|
||||
return cache.getInstance(pattern, null, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a formatter instance using the specified pattern, time zone
|
||||
* and locale.</p>
|
||||
*
|
||||
* @param pattern {@link java.text.SimpleDateFormat} compatible
|
||||
* pattern
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a pattern based date/time formatter
|
||||
* @throws IllegalArgumentException if pattern is invalid
|
||||
* or {@code null}
|
||||
*/
|
||||
public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
|
||||
return cache.getInstance(pattern, timeZone, locale);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets a date formatter instance using the specified style in the
|
||||
* default time zone and locale.</p>
|
||||
*
|
||||
* @param style date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @return a localized standard date formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getDateInstance(final int style) {
|
||||
return cache.getDateInstance(style, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date formatter instance using the specified style and
|
||||
* locale in the default time zone.</p>
|
||||
*
|
||||
* @param style date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getDateInstance(final int style, final Locale locale) {
|
||||
return cache.getDateInstance(style, null, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date formatter instance using the specified style and
|
||||
* time zone in the default locale.</p>
|
||||
*
|
||||
* @param style date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date
|
||||
* @return a localized standard date formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
|
||||
return cache.getDateInstance(style, timeZone, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date formatter instance using the specified style, time
|
||||
* zone and locale.</p>
|
||||
*
|
||||
* @param style date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date
|
||||
* pattern defined
|
||||
*/
|
||||
public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
|
||||
return cache.getDateInstance(style, timeZone, locale);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets a time formatter instance using the specified style in the
|
||||
* default time zone and locale.</p>
|
||||
*
|
||||
* @param style time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @return a localized standard time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no time
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getTimeInstance(final int style) {
|
||||
return cache.getTimeInstance(style, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a time formatter instance using the specified style and
|
||||
* locale in the default time zone.</p>
|
||||
*
|
||||
* @param style time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no time
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
|
||||
return cache.getTimeInstance(style, null, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a time formatter instance using the specified style and
|
||||
* time zone in the default locale.</p>
|
||||
*
|
||||
* @param style time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted time
|
||||
* @return a localized standard time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no time
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
|
||||
return cache.getTimeInstance(style, timeZone, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a time formatter instance using the specified style, time
|
||||
* zone and locale.</p>
|
||||
*
|
||||
* @param style time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted time
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no time
|
||||
* pattern defined
|
||||
*/
|
||||
public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
|
||||
return cache.getTimeInstance(style, timeZone, locale);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets a date/time formatter instance using the specified style
|
||||
* in the default time zone and locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
|
||||
return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date/time formatter instance using the specified style and
|
||||
* locale in the default time zone.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
|
||||
return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date/time formatter instance using the specified style and
|
||||
* time zone in the default locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
* @since 2.1
|
||||
*/
|
||||
public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
|
||||
return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
|
||||
}
|
||||
/**
|
||||
* <p>Gets a date/time formatter instance using the specified style,
|
||||
* time zone and locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
*/
|
||||
public static FastDateFormat getDateTimeInstance(
|
||||
final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
|
||||
return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Constructs a new FastDateFormat.</p>
|
||||
*
|
||||
* @param pattern {@link java.text.SimpleDateFormat} compatible pattern
|
||||
* @param timeZone non-null time zone to use
|
||||
* @param locale non-null locale to use
|
||||
* @throws NullPointerException if pattern, timeZone, or locale is null.
|
||||
*/
|
||||
protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
|
||||
this(pattern, timeZone, locale, null);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Constructs a new FastDateFormat.</p>
|
||||
*
|
||||
* @param pattern {@link java.text.SimpleDateFormat} compatible pattern
|
||||
* @param timeZone non-null time zone to use
|
||||
* @param locale non-null locale to use
|
||||
* @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years
|
||||
* @throws NullPointerException if pattern, timeZone, or locale is null.
|
||||
*/
|
||||
protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
|
||||
printer= new FastDatePrinter(pattern, timeZone, locale);
|
||||
parser= new FastDateParser(pattern, timeZone, locale, centuryStart);
|
||||
}
|
||||
|
||||
// Format methods
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Formats a {@code Date}, {@code Calendar} or
|
||||
* {@code Long} (milliseconds) object.</p>
|
||||
* This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)}
|
||||
*
|
||||
* @param obj the object to format
|
||||
* @param toAppendTo the buffer to append to
|
||||
* @param pos the position - ignored
|
||||
* @return the buffer passed in
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
|
||||
return toAppendTo.append(printer.format(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @return the formatted string
|
||||
* @since 2.1
|
||||
*/
|
||||
@Override
|
||||
public String format(final long millis) {
|
||||
return printer.format(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @return the formatted string
|
||||
*/
|
||||
@Override
|
||||
public String format(final Date date) {
|
||||
return printer.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object.</p>
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @return the formatted string
|
||||
*/
|
||||
@Override
|
||||
public String format(final Calendar calendar) {
|
||||
return printer.format(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @since 2.1
|
||||
* @deprecated Use {{@link #format(long, Appendable)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public StringBuffer format(final long millis, final StringBuffer buf) {
|
||||
return printer.format(millis, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object into the
|
||||
* supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @deprecated Use {{@link #format(Date, Appendable)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public StringBuffer format(final Date date, final StringBuffer buf) {
|
||||
return printer.format(date, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @deprecated Use {{@link #format(Calendar, Appendable)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
|
||||
return printer.format(calendar, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a millisecond {@code long} value into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param millis the millisecond value to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @since 3.5
|
||||
*/
|
||||
@Override
|
||||
public <B extends Appendable> B format(final long millis, final B buf) {
|
||||
return printer.format(millis, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Date} object into the
|
||||
* supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
|
||||
*
|
||||
* @param date the date to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @since 3.5
|
||||
*/
|
||||
@Override
|
||||
public <B extends Appendable> B format(final Date date, final B buf) {
|
||||
return printer.format(date, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats a {@code Calendar} object into the
|
||||
* supplied {@code StringBuffer}.</p>
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @since 3.5
|
||||
*/
|
||||
@Override
|
||||
public <B extends Appendable> B format(final Calendar calendar, final B buf) {
|
||||
return printer.format(calendar, buf);
|
||||
}
|
||||
|
||||
// Parsing
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#parse(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Date parse(final String source) throws ParseException {
|
||||
return parser.parse(source);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#parse(java.lang.String, java.text.ParsePosition)
|
||||
*/
|
||||
@Override
|
||||
public Date parse(final String source, final ParsePosition pos) {
|
||||
return parser.parse(source, pos);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see DateParser#parse(java.lang.String, java.text.ParsePosition, java.util.Calendar)
|
||||
*/
|
||||
@Override
|
||||
public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
|
||||
return parser.parse(source, pos, calendar);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
|
||||
*/
|
||||
@Override
|
||||
public Object parseObject(final String source, final ParsePosition pos) {
|
||||
return parser.parseObject(source, pos);
|
||||
}
|
||||
|
||||
// Accessors
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the pattern used by this formatter.</p>
|
||||
*
|
||||
* @return the pattern, {@link java.text.SimpleDateFormat} compatible
|
||||
*/
|
||||
@Override
|
||||
public String getPattern() {
|
||||
return printer.getPattern();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the time zone used by this formatter.</p>
|
||||
*
|
||||
* <p>This zone is always used for {@code Date} formatting. </p>
|
||||
*
|
||||
* @return the time zone
|
||||
*/
|
||||
@Override
|
||||
public TimeZone getTimeZone() {
|
||||
return printer.getTimeZone();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the locale used by this formatter.</p>
|
||||
*
|
||||
* @return the locale
|
||||
*/
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return printer.getLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets an estimate for the maximum string length that the
|
||||
* formatter will produce.</p>
|
||||
*
|
||||
* <p>The actual formatted length will almost always be less than or
|
||||
* equal to this amount.</p>
|
||||
*
|
||||
* @return the maximum formatted length
|
||||
*/
|
||||
public int getMaxLengthEstimate() {
|
||||
return printer.getMaxLengthEstimate();
|
||||
}
|
||||
|
||||
// Basics
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compares two objects for equality.</p>
|
||||
*
|
||||
* @param obj the object to compare to
|
||||
* @return {@code true} if equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof FastDateFormat == false) {
|
||||
return false;
|
||||
}
|
||||
final FastDateFormat other = (FastDateFormat) obj;
|
||||
// no need to check parser, as it has same invariants as printer
|
||||
return printer.equals(other.printer);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a hash code compatible with equals.</p>
|
||||
*
|
||||
* @return a hash code compatible with equals
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return printer.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a debugging string version of this formatter.</p>
|
||||
*
|
||||
* @return a debugging string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Performs the formatting by applying the rules to the
|
||||
* specified calendar.</p>
|
||||
*
|
||||
* @param calendar the calendar to format
|
||||
* @param buf the buffer to format into
|
||||
* @return the specified string buffer
|
||||
* @deprecated Use {@link #format(Calendar, Appendable)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
|
||||
return printer.applyRules(calendar, buf);
|
||||
}
|
||||
}
|
@ -1,997 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.time;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.text.DateFormatSymbols;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>FastDateParser is a fast and thread-safe version of
|
||||
* {@link java.text.SimpleDateFormat}.</p>
|
||||
*
|
||||
* <p>To obtain a proxy to a FastDateParser, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}
|
||||
* or another variation of the factory methods of {@link FastDateFormat}.</p>
|
||||
*
|
||||
* <p>Since FastDateParser is thread safe, you can use a static member instance:</p>
|
||||
* <code>
|
||||
* private static final DateParser DATE_PARSER = FastDateFormat.getInstance("yyyy-MM-dd");
|
||||
* </code>
|
||||
*
|
||||
* <p>This class can be used as a direct replacement for
|
||||
* <code>SimpleDateFormat</code> in most parsing situations.
|
||||
* This class is especially useful in multi-threaded server environments.
|
||||
* <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
|
||||
* nor will it be as Sun has closed the
|
||||
* <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335">bug</a>/RFE.
|
||||
* </p>
|
||||
*
|
||||
* <p>Only parsing is supported by this class, but all patterns are compatible with
|
||||
* SimpleDateFormat.</p>
|
||||
*
|
||||
* <p>The class operates in lenient mode, so for example a time of 90 minutes is treated as 1 hour 30 minutes.</p>
|
||||
*
|
||||
* <p>Timing tests indicate this class is as about as fast as SimpleDateFormat
|
||||
* in single thread applications and about 25% faster in multi-thread applications.</p>
|
||||
*
|
||||
* @since 3.2
|
||||
* @see FastDatePrinter
|
||||
*/
|
||||
public class FastDateParser implements DateParser, Serializable {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 3L;
|
||||
|
||||
static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP");
|
||||
|
||||
// defining fields
|
||||
private final String pattern;
|
||||
private final TimeZone timeZone;
|
||||
private final Locale locale;
|
||||
private final int century;
|
||||
private final int startYear;
|
||||
|
||||
// derived fields
|
||||
private transient List<StrategyAndWidth> patterns;
|
||||
|
||||
// comparator used to sort regex alternatives
|
||||
// alternatives should be ordered longer first, and shorter last. ('february' before 'feb')
|
||||
// all entries must be lowercase by locale.
|
||||
private static final Comparator<String> LONGER_FIRST_LOWERCASE = new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(final String left, final String right) {
|
||||
return right.compareTo(left);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* <p>Constructs a new FastDateParser.</p>
|
||||
*
|
||||
* Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the
|
||||
* factory methods of {@link FastDateFormat} to get a cached FastDateParser instance.
|
||||
*
|
||||
* @param pattern non-null {@link java.text.SimpleDateFormat} compatible
|
||||
* pattern
|
||||
* @param timeZone non-null time zone to use
|
||||
* @param locale non-null locale
|
||||
*/
|
||||
protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
|
||||
this(pattern, timeZone, locale, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructs a new FastDateParser.</p>
|
||||
*
|
||||
* @param pattern non-null {@link java.text.SimpleDateFormat} compatible
|
||||
* pattern
|
||||
* @param timeZone non-null time zone to use
|
||||
* @param locale non-null locale
|
||||
* @param centuryStart The start of the century for 2 digit year parsing
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
|
||||
this.pattern = pattern;
|
||||
this.timeZone = timeZone;
|
||||
this.locale = locale;
|
||||
|
||||
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
|
||||
|
||||
int centuryStartYear;
|
||||
if(centuryStart!=null) {
|
||||
definingCalendar.setTime(centuryStart);
|
||||
centuryStartYear= definingCalendar.get(Calendar.YEAR);
|
||||
} else if(locale.equals(JAPANESE_IMPERIAL)) {
|
||||
centuryStartYear= 0;
|
||||
} else {
|
||||
// from 80 years ago to 20 years from now
|
||||
definingCalendar.setTime(new Date());
|
||||
centuryStartYear= definingCalendar.get(Calendar.YEAR)-80;
|
||||
}
|
||||
century= centuryStartYear / 100 * 100;
|
||||
startYear= centuryStartYear - century;
|
||||
|
||||
init(definingCalendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize derived fields from defining fields.
|
||||
* This is called from constructor and from readObject (de-serialization)
|
||||
*
|
||||
* @param definingCalendar the {@link Calendar} instance used to initialize this FastDateParser
|
||||
*/
|
||||
private void init(final Calendar definingCalendar) {
|
||||
patterns = new ArrayList<>();
|
||||
|
||||
final StrategyParser fm = new StrategyParser(definingCalendar);
|
||||
for(;;) {
|
||||
final StrategyAndWidth field = fm.getNextStrategy();
|
||||
if(field==null) {
|
||||
break;
|
||||
}
|
||||
patterns.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
// helper classes to parse the format string
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Holds strategy and field width
|
||||
*/
|
||||
private static class StrategyAndWidth {
|
||||
final Strategy strategy;
|
||||
final int width;
|
||||
|
||||
StrategyAndWidth(final Strategy strategy, final int width) {
|
||||
this.strategy = strategy;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
int getMaxWidth(final ListIterator<StrategyAndWidth> lt) {
|
||||
if(!strategy.isNumber() || !lt.hasNext()) {
|
||||
return 0;
|
||||
}
|
||||
final Strategy nextStrategy = lt.next().strategy;
|
||||
lt.previous();
|
||||
return nextStrategy.isNumber() ?width :0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse format into Strategies
|
||||
*/
|
||||
private class StrategyParser {
|
||||
private final Calendar definingCalendar;
|
||||
private int currentIdx;
|
||||
|
||||
StrategyParser(final Calendar definingCalendar) {
|
||||
this.definingCalendar = definingCalendar;
|
||||
}
|
||||
|
||||
StrategyAndWidth getNextStrategy() {
|
||||
if (currentIdx >= pattern.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final char c = pattern.charAt(currentIdx);
|
||||
if (isFormatLetter(c)) {
|
||||
return letterPattern(c);
|
||||
}
|
||||
return literal();
|
||||
}
|
||||
|
||||
private StrategyAndWidth letterPattern(final char c) {
|
||||
final int begin = currentIdx;
|
||||
while (++currentIdx < pattern.length()) {
|
||||
if (pattern.charAt(currentIdx) != c) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final int width = currentIdx - begin;
|
||||
return new StrategyAndWidth(getStrategy(c, width, definingCalendar), width);
|
||||
}
|
||||
|
||||
private StrategyAndWidth literal() {
|
||||
boolean activeQuote = false;
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (currentIdx < pattern.length()) {
|
||||
final char c = pattern.charAt(currentIdx);
|
||||
if (!activeQuote && isFormatLetter(c)) {
|
||||
break;
|
||||
} else if (c == '\'' && (++currentIdx == pattern.length() || pattern.charAt(currentIdx) != '\'')) {
|
||||
activeQuote = !activeQuote;
|
||||
continue;
|
||||
}
|
||||
++currentIdx;
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
if (activeQuote) {
|
||||
throw new IllegalArgumentException("Unterminated quote");
|
||||
}
|
||||
|
||||
final String formatField = sb.toString();
|
||||
return new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFormatLetter(final char c) {
|
||||
return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
|
||||
}
|
||||
|
||||
// Accessors
|
||||
//-----------------------------------------------------------------------
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#getPattern()
|
||||
*/
|
||||
@Override
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#getTimeZone()
|
||||
*/
|
||||
@Override
|
||||
public TimeZone getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#getLocale()
|
||||
*/
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
|
||||
// Basics
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compare another object for equality with this object.</p>
|
||||
*
|
||||
* @param obj the object to compare to
|
||||
* @return <code>true</code>if equal to this instance
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof FastDateParser)) {
|
||||
return false;
|
||||
}
|
||||
final FastDateParser other = (FastDateParser) obj;
|
||||
return pattern.equals(other.pattern)
|
||||
&& timeZone.equals(other.timeZone)
|
||||
&& locale.equals(other.locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return a hash code compatible with equals.</p>
|
||||
*
|
||||
* @return a hash code compatible with equals
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get a string version of this formatter.</p>
|
||||
*
|
||||
* @return a debugging string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FastDateParser[" + pattern + "," + locale + "," + timeZone.getID() + "]";
|
||||
}
|
||||
|
||||
// Serializing
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Create the object after serialization. This implementation reinitializes the
|
||||
* transient properties.
|
||||
*
|
||||
* @param in ObjectInputStream from which the object is being deserialized.
|
||||
* @throws IOException if there is an IO issue.
|
||||
* @throws ClassNotFoundException if a class cannot be found.
|
||||
*/
|
||||
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
|
||||
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
|
||||
init(definingCalendar);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#parseObject(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Object parseObject(final String source) throws ParseException {
|
||||
return parse(source);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#parse(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Date parse(final String source) throws ParseException {
|
||||
final ParsePosition pp = new ParsePosition(0);
|
||||
final Date date= parse(source, pp);
|
||||
if (date == null) {
|
||||
// Add a note re supported date range
|
||||
if (locale.equals(JAPANESE_IMPERIAL)) {
|
||||
throw new ParseException(
|
||||
"(The " +locale + " locale does not support dates before 1868 AD)\n" +
|
||||
"Unparseable date: \""+source, pp.getErrorIndex());
|
||||
}
|
||||
throw new ParseException("Unparseable date: "+source, pp.getErrorIndex());
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#parseObject(java.lang.String, java.text.ParsePosition)
|
||||
*/
|
||||
@Override
|
||||
public Object parseObject(final String source, final ParsePosition pos) {
|
||||
return parse(source, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation updates the ParsePosition if the parse succeeds.
|
||||
* However, it sets the error index to the position before the failed field unlike
|
||||
* the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets
|
||||
* the error index to after the failed field.
|
||||
* <p>
|
||||
* To determine if the parse has succeeded, the caller must check if the current parse position
|
||||
* given by {@link ParsePosition#getIndex()} has been updated. If the input buffer has been fully
|
||||
* parsed, then the index will point to just after the end of the input buffer.
|
||||
*
|
||||
* @see DateParser#parse(String, ParsePosition)
|
||||
*/
|
||||
@Override
|
||||
public Date parse(final String source, final ParsePosition pos) {
|
||||
// timing tests indicate getting new instance is 19% faster than cloning
|
||||
final Calendar cal= Calendar.getInstance(timeZone, locale);
|
||||
cal.clear();
|
||||
|
||||
return parse(source, pos, cal) ? cal.getTime() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a formatted date string according to the format. Updates the Calendar with parsed fields.
|
||||
* Upon success, the ParsePosition index is updated to indicate how much of the source text was consumed.
|
||||
* Not all source text needs to be consumed. Upon parse failure, ParsePosition error index is updated to
|
||||
* the offset of the source text which does not match the supplied format.
|
||||
*
|
||||
* @param source The text to parse.
|
||||
* @param pos On input, the position in the source to start parsing, on output, updated position.
|
||||
* @param calendar The calendar into which to set parsed fields.
|
||||
* @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated)
|
||||
* @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is
|
||||
* out of range.
|
||||
*/
|
||||
@Override
|
||||
public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
|
||||
final ListIterator<StrategyAndWidth> lt = patterns.listIterator();
|
||||
while (lt.hasNext()) {
|
||||
final StrategyAndWidth strategyAndWidth = lt.next();
|
||||
final int maxWidth = strategyAndWidth.getMaxWidth(lt);
|
||||
if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Support for strategies
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
|
||||
for (int i = 0; i < value.length(); ++i) {
|
||||
final char c = value.charAt(i);
|
||||
switch (c) {
|
||||
case '\\':
|
||||
case '^':
|
||||
case '$':
|
||||
case '.':
|
||||
case '|':
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case '{':
|
||||
sb.append('\\');
|
||||
default:
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the short and long values displayed for a field
|
||||
* @param cal The calendar to obtain the short and long values
|
||||
* @param locale The locale of display names
|
||||
* @param field The field of interest
|
||||
* @param regex The regular expression to build
|
||||
* @return The map of string display names to field values
|
||||
*/
|
||||
private static Map<String, Integer> appendDisplayNames(final Calendar cal, final Locale locale, final int field, final StringBuilder regex) {
|
||||
final Map<String, Integer> values = new HashMap<>();
|
||||
|
||||
final Map<String, Integer> displayNames = cal.getDisplayNames(field, Calendar.ALL_STYLES, locale);
|
||||
final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
|
||||
for (final Map.Entry<String, Integer> displayName : displayNames.entrySet()) {
|
||||
final String key = displayName.getKey().toLowerCase(locale);
|
||||
if (sorted.add(key)) {
|
||||
values.put(key, displayName.getValue());
|
||||
}
|
||||
}
|
||||
for (final String symbol : sorted) {
|
||||
simpleQuote(regex, symbol).append('|');
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust dates to be within appropriate century
|
||||
* @param twoDigitYear The year to adjust
|
||||
* @return A value between centuryStart(inclusive) to centuryStart+100(exclusive)
|
||||
*/
|
||||
private int adjustYear(final int twoDigitYear) {
|
||||
final int trial = century + twoDigitYear;
|
||||
return twoDigitYear >= startYear ? trial : trial + 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* A strategy to parse a single field from the parsing pattern
|
||||
*/
|
||||
private abstract static class Strategy {
|
||||
/**
|
||||
* Is this field a number?
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @return true, if field is a number
|
||||
*/
|
||||
boolean isNumber() {
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* A strategy to parse a single field from the parsing pattern
|
||||
*/
|
||||
private abstract static class PatternStrategy extends Strategy {
|
||||
|
||||
private Pattern pattern;
|
||||
|
||||
void createPattern(final StringBuilder regex) {
|
||||
createPattern(regex.toString());
|
||||
}
|
||||
|
||||
void createPattern(final String regex) {
|
||||
this.pattern = Pattern.compile(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this field a number?
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @return true, if field is a number
|
||||
*/
|
||||
@Override
|
||||
boolean isNumber() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
|
||||
final Matcher matcher = pattern.matcher(source.substring(pos.getIndex()));
|
||||
if (!matcher.lookingAt()) {
|
||||
pos.setErrorIndex(pos.getIndex());
|
||||
return false;
|
||||
}
|
||||
pos.setIndex(pos.getIndex() + matcher.end(1));
|
||||
setCalendar(parser, calendar, matcher.group(1));
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract void setCalendar(FastDateParser parser, Calendar cal, String value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a Strategy given a field from a SimpleDateFormat pattern
|
||||
* @param f A sub-sequence of the SimpleDateFormat pattern
|
||||
* @param definingCalendar The calendar to obtain the short and long values
|
||||
* @return The Strategy that will handle parsing for the field
|
||||
*/
|
||||
private Strategy getStrategy(final char f, final int width, final Calendar definingCalendar) {
|
||||
switch(f) {
|
||||
default:
|
||||
throw new IllegalArgumentException("Format '"+f+"' not supported");
|
||||
case 'D':
|
||||
return DAY_OF_YEAR_STRATEGY;
|
||||
case 'E':
|
||||
return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
|
||||
case 'F':
|
||||
return DAY_OF_WEEK_IN_MONTH_STRATEGY;
|
||||
case 'G':
|
||||
return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
|
||||
case 'H': // Hour in day (0-23)
|
||||
return HOUR_OF_DAY_STRATEGY;
|
||||
case 'K': // Hour in am/pm (0-11)
|
||||
return HOUR_STRATEGY;
|
||||
case 'M':
|
||||
return width>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
|
||||
case 'S':
|
||||
return MILLISECOND_STRATEGY;
|
||||
case 'W':
|
||||
return WEEK_OF_MONTH_STRATEGY;
|
||||
case 'a':
|
||||
return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
|
||||
case 'd':
|
||||
return DAY_OF_MONTH_STRATEGY;
|
||||
case 'h': // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
|
||||
return HOUR12_STRATEGY;
|
||||
case 'k': // Hour in day (1-24), i.e. midnight is 24, not 0
|
||||
return HOUR24_OF_DAY_STRATEGY;
|
||||
case 'm':
|
||||
return MINUTE_STRATEGY;
|
||||
case 's':
|
||||
return SECOND_STRATEGY;
|
||||
case 'u':
|
||||
return DAY_OF_WEEK_STRATEGY;
|
||||
case 'w':
|
||||
return WEEK_OF_YEAR_STRATEGY;
|
||||
case 'y':
|
||||
case 'Y':
|
||||
return width>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
|
||||
case 'X':
|
||||
return ISO8601TimeZoneStrategy.getStrategy(width);
|
||||
case 'Z':
|
||||
if (width==2) {
|
||||
return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY;
|
||||
}
|
||||
//$FALL-THROUGH$
|
||||
case 'z':
|
||||
return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // OK because we are creating an array with no entries
|
||||
private static final ConcurrentMap<Locale, Strategy>[] caches = new ConcurrentMap[Calendar.FIELD_COUNT];
|
||||
|
||||
/**
|
||||
* Get a cache of Strategies for a particular field
|
||||
* @param field The Calendar field
|
||||
* @return a cache of Locale to Strategy
|
||||
*/
|
||||
private static ConcurrentMap<Locale, Strategy> getCache(final int field) {
|
||||
synchronized (caches) {
|
||||
if (caches[field] == null) {
|
||||
caches[field] = new ConcurrentHashMap<>(3);
|
||||
}
|
||||
return caches[field];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a Text field
|
||||
* @param field The Calendar field
|
||||
* @param definingCalendar The calendar to obtain the short and long values
|
||||
* @return a TextStrategy for the field and Locale
|
||||
*/
|
||||
private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) {
|
||||
final ConcurrentMap<Locale, Strategy> cache = getCache(field);
|
||||
Strategy strategy = cache.get(locale);
|
||||
if (strategy == null) {
|
||||
strategy = field == Calendar.ZONE_OFFSET
|
||||
? new TimeZoneStrategy(locale)
|
||||
: new CaseInsensitiveTextStrategy(field, definingCalendar, locale);
|
||||
final Strategy inCache = cache.putIfAbsent(locale, strategy);
|
||||
if (inCache != null) {
|
||||
return inCache;
|
||||
}
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* A strategy that copies the static or quoted field in the parsing pattern
|
||||
*/
|
||||
private static class CopyQuotedStrategy extends Strategy {
|
||||
|
||||
private final String formatField;
|
||||
|
||||
/**
|
||||
* Construct a Strategy that ensures the formatField has literal text
|
||||
* @param formatField The literal text to match
|
||||
*/
|
||||
CopyQuotedStrategy(final String formatField) {
|
||||
this.formatField = formatField;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
boolean isNumber() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
|
||||
for (int idx = 0; idx < formatField.length(); ++idx) {
|
||||
final int sIdx = idx + pos.getIndex();
|
||||
if (sIdx == source.length()) {
|
||||
pos.setErrorIndex(sIdx);
|
||||
return false;
|
||||
}
|
||||
if (formatField.charAt(idx) != source.charAt(sIdx)) {
|
||||
pos.setErrorIndex(sIdx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pos.setIndex(formatField.length() + pos.getIndex());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A strategy that handles a text field in the parsing pattern
|
||||
*/
|
||||
private static class CaseInsensitiveTextStrategy extends PatternStrategy {
|
||||
private final int field;
|
||||
final Locale locale;
|
||||
private final Map<String, Integer> lKeyValues;
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a Text field
|
||||
* @param field The Calendar field
|
||||
* @param definingCalendar The Calendar to use
|
||||
* @param locale The Locale to use
|
||||
*/
|
||||
CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) {
|
||||
this.field = field;
|
||||
this.locale = locale;
|
||||
|
||||
final StringBuilder regex = new StringBuilder();
|
||||
regex.append("((?iu)");
|
||||
lKeyValues = appendDisplayNames(definingCalendar, locale, field, regex);
|
||||
regex.setLength(regex.length()-1);
|
||||
regex.append(")");
|
||||
createPattern(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
|
||||
final Integer iVal = lKeyValues.get(value.toLowerCase(locale));
|
||||
cal.set(field, iVal.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A strategy that handles a number field in the parsing pattern
|
||||
*/
|
||||
private static class NumberStrategy extends Strategy {
|
||||
private final int field;
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a Number field
|
||||
* @param field The Calendar field
|
||||
*/
|
||||
NumberStrategy(final int field) {
|
||||
this.field= field;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
boolean isNumber() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
|
||||
int idx = pos.getIndex();
|
||||
int last = source.length();
|
||||
|
||||
if (maxWidth == 0) {
|
||||
// if no maxWidth, strip leading white space
|
||||
for (; idx < last; ++idx) {
|
||||
final char c = source.charAt(idx);
|
||||
if (!Character.isWhitespace(c)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos.setIndex(idx);
|
||||
} else {
|
||||
final int end = idx + maxWidth;
|
||||
if (last > end) {
|
||||
last = end;
|
||||
}
|
||||
}
|
||||
|
||||
for (; idx < last; ++idx) {
|
||||
final char c = source.charAt(idx);
|
||||
if (!Character.isDigit(c)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos.getIndex() == idx) {
|
||||
pos.setErrorIndex(idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
final int value = Integer.parseInt(source.substring(pos.getIndex(), idx));
|
||||
pos.setIndex(idx);
|
||||
|
||||
calendar.set(field, modify(parser, value));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make any modifications to parsed integer
|
||||
* @param parser The parser
|
||||
* @param iValue The parsed integer
|
||||
* @return The modified value
|
||||
*/
|
||||
int modify(final FastDateParser parser, final int iValue) {
|
||||
return iValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
int modify(final FastDateParser parser, final int iValue) {
|
||||
return iValue < 100 ? parser.adjustYear(iValue) : iValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A strategy that handles a timezone field in the parsing pattern
|
||||
*/
|
||||
static class TimeZoneStrategy extends PatternStrategy {
|
||||
private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
|
||||
private static final String GMT_OPTION= "GMT[+-]\\d{1,2}:\\d{2}";
|
||||
|
||||
private final Locale locale;
|
||||
private final Map<String, TzInfo> tzNames= new HashMap<>();
|
||||
|
||||
private static class TzInfo {
|
||||
TimeZone zone;
|
||||
int dstOffset;
|
||||
|
||||
TzInfo(final TimeZone tz, final boolean useDst) {
|
||||
zone = tz;
|
||||
dstOffset = useDst ?tz.getDSTSavings() :0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Index of zone id
|
||||
*/
|
||||
private static final int ID = 0;
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a TimeZone
|
||||
* @param locale The Locale
|
||||
*/
|
||||
TimeZoneStrategy(final Locale locale) {
|
||||
this.locale = locale;
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION );
|
||||
|
||||
final Set<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
|
||||
|
||||
final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
|
||||
for (final String[] zoneNames : zones) {
|
||||
// offset 0 is the time zone ID and is not localized
|
||||
final String tzId = zoneNames[ID];
|
||||
if (tzId.equalsIgnoreCase("GMT")) {
|
||||
continue;
|
||||
}
|
||||
final TimeZone tz = TimeZone.getTimeZone(tzId);
|
||||
// offset 1 is long standard name
|
||||
// offset 2 is short standard name
|
||||
final TzInfo standard = new TzInfo(tz, false);
|
||||
TzInfo tzInfo = standard;
|
||||
for (int i = 1; i < zoneNames.length; ++i) {
|
||||
switch (i) {
|
||||
case 3: // offset 3 is long daylight savings (or summertime) name
|
||||
// offset 4 is the short summertime name
|
||||
tzInfo = new TzInfo(tz, true);
|
||||
break;
|
||||
case 5: // offset 5 starts additional names, probably standard time
|
||||
tzInfo = standard;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (zoneNames[i] != null) {
|
||||
final String key = zoneNames[i].toLowerCase(locale);
|
||||
// ignore the data associated with duplicates supplied in
|
||||
// the additional names
|
||||
if (sorted.add(key)) {
|
||||
tzNames.put(key, tzInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// order the regex alternatives with longer strings first, greedy
|
||||
// match will ensure longest string will be consumed
|
||||
for (final String zoneName : sorted) {
|
||||
simpleQuote(sb.append('|'), zoneName);
|
||||
}
|
||||
sb.append(")");
|
||||
createPattern(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
|
||||
if (value.charAt(0) == '+' || value.charAt(0) == '-') {
|
||||
final TimeZone tz = TimeZone.getTimeZone("GMT" + value);
|
||||
cal.setTimeZone(tz);
|
||||
} else if (value.regionMatches(true, 0, "GMT", 0, 3)) {
|
||||
final TimeZone tz = TimeZone.getTimeZone(value.toUpperCase());
|
||||
cal.setTimeZone(tz);
|
||||
} else {
|
||||
final TzInfo tzInfo = tzNames.get(value.toLowerCase(locale));
|
||||
cal.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
|
||||
cal.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ISO8601TimeZoneStrategy extends PatternStrategy {
|
||||
// Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a TimeZone
|
||||
* @param pattern The Pattern
|
||||
*/
|
||||
ISO8601TimeZoneStrategy(final String pattern) {
|
||||
createPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
|
||||
if (value.equals("Z")) {
|
||||
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
} else {
|
||||
cal.setTimeZone(TimeZone.getTimeZone("GMT" + value));
|
||||
}
|
||||
}
|
||||
|
||||
private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))");
|
||||
private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))");
|
||||
private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))");
|
||||
|
||||
/**
|
||||
* Factory method for ISO8601TimeZoneStrategies.
|
||||
*
|
||||
* @param tokenLen a token indicating the length of the TimeZone String to be formatted.
|
||||
* @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code tokenLen}. If no such
|
||||
* strategy exists, an IllegalArgumentException will be thrown.
|
||||
*/
|
||||
static Strategy getStrategy(final int tokenLen) {
|
||||
switch(tokenLen) {
|
||||
case 1:
|
||||
return ISO_8601_1_STRATEGY;
|
||||
case 2:
|
||||
return ISO_8601_2_STRATEGY;
|
||||
case 3:
|
||||
return ISO_8601_3_STRATEGY;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid number of X");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
|
||||
@Override
|
||||
int modify(final FastDateParser parser, final int iValue) {
|
||||
return iValue-1;
|
||||
}
|
||||
};
|
||||
private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
|
||||
private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
|
||||
private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH);
|
||||
private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
|
||||
private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
|
||||
private static final Strategy DAY_OF_WEEK_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK) {
|
||||
@Override
|
||||
int modify(final FastDateParser parser, final int iValue) {
|
||||
return iValue != 7 ? iValue + 1 : Calendar.SUNDAY;
|
||||
}
|
||||
};
|
||||
private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
|
||||
private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
|
||||
private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
|
||||
@Override
|
||||
int modify(final FastDateParser parser, final int iValue) {
|
||||
return iValue == 24 ? 0 : iValue;
|
||||
}
|
||||
};
|
||||
private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
|
||||
@Override
|
||||
int modify(final FastDateParser parser, final int iValue) {
|
||||
return iValue == 12 ? 0 : iValue;
|
||||
}
|
||||
};
|
||||
private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR);
|
||||
private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
|
||||
private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
|
||||
private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,273 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Liu Dong
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.qihoo360.replugin.ext.lang3.time;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.Format;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.Validate;
|
||||
|
||||
/**
|
||||
* <p>FormatCache is a cache and factory for {@link Format}s.</p>
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
// TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach.
|
||||
abstract class FormatCache<F extends Format> {
|
||||
|
||||
/**
|
||||
* No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG
|
||||
*/
|
||||
static final int NONE= -1;
|
||||
|
||||
private final ConcurrentMap<MultipartKey, F> cInstanceCache
|
||||
= new ConcurrentHashMap<>(7);
|
||||
|
||||
private static final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
|
||||
= new ConcurrentHashMap<>(7);
|
||||
|
||||
/**
|
||||
* <p>Gets a formatter instance using the default pattern in the
|
||||
* default timezone and locale.</p>
|
||||
*
|
||||
* @return a date/time formatter
|
||||
*/
|
||||
public F getInstance() {
|
||||
return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a formatter instance using the specified pattern, time zone
|
||||
* and locale.</p>
|
||||
*
|
||||
* @param pattern {@link SimpleDateFormat} compatible
|
||||
* pattern, non-null
|
||||
* @param timeZone the time zone, null means use the default TimeZone
|
||||
* @param locale the locale, null means use the default Locale
|
||||
* @return a pattern based date/time formatter
|
||||
* @throws IllegalArgumentException if pattern is invalid
|
||||
* or <code>null</code>
|
||||
*/
|
||||
public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {
|
||||
Validate.notNull(pattern, "pattern must not be null");
|
||||
if (timeZone == null) {
|
||||
timeZone = TimeZone.getDefault();
|
||||
}
|
||||
if (locale == null) {
|
||||
locale = Locale.getDefault();
|
||||
}
|
||||
final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
|
||||
F format = cInstanceCache.get(key);
|
||||
if (format == null) {
|
||||
format = createInstance(pattern, timeZone, locale);
|
||||
final F previousValue= cInstanceCache.putIfAbsent(key, format);
|
||||
if (previousValue != null) {
|
||||
// another thread snuck in and did the same work
|
||||
// we should return the instance that is in ConcurrentMap
|
||||
format= previousValue;
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Create a format instance using the specified pattern, time zone
|
||||
* and locale.</p>
|
||||
*
|
||||
* @param pattern {@link SimpleDateFormat} compatible pattern, this will not be null.
|
||||
* @param timeZone time zone, this will not be null.
|
||||
* @param locale locale, this will not be null.
|
||||
* @return a pattern based date/time formatter
|
||||
* @throws IllegalArgumentException if pattern is invalid
|
||||
* or <code>null</code>
|
||||
*/
|
||||
protected abstract F createInstance(String pattern, TimeZone timeZone, Locale locale);
|
||||
|
||||
/**
|
||||
* <p>Gets a date/time formatter instance using the specified style,
|
||||
* time zone and locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date, null means use default Locale
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
*/
|
||||
// This must remain private, see LANG-884
|
||||
private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
|
||||
if (locale == null) {
|
||||
locale = Locale.getDefault();
|
||||
}
|
||||
final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
|
||||
return getInstance(pattern, timeZone, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date/time formatter instance using the specified style,
|
||||
* time zone and locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date, null means use default Locale
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
*/
|
||||
// package protected, for access from FastDateFormat; do not make public or protected
|
||||
F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
|
||||
return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date formatter instance using the specified style,
|
||||
* time zone and locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date, null means use default Locale
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
*/
|
||||
// package protected, for access from FastDateFormat; do not make public or protected
|
||||
F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale locale) {
|
||||
return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a time formatter instance using the specified style,
|
||||
* time zone and locale.</p>
|
||||
*
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
|
||||
* @param timeZone optional time zone, overrides time zone of
|
||||
* formatted date, null means use default Locale
|
||||
* @param locale optional locale, overrides system locale
|
||||
* @return a localized standard date/time formatter
|
||||
* @throws IllegalArgumentException if the Locale has no date/time
|
||||
* pattern defined
|
||||
*/
|
||||
// package protected, for access from FastDateFormat; do not make public or protected
|
||||
F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale locale) {
|
||||
return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a date/time format for the specified styles and locale.</p>
|
||||
*
|
||||
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format
|
||||
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format
|
||||
* @param locale The non-null locale of the desired format
|
||||
* @return a localized standard date/time format
|
||||
* @throws IllegalArgumentException if the Locale has no date/time pattern defined
|
||||
*/
|
||||
// package protected, for access from test code; do not make public or protected
|
||||
static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
|
||||
final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
|
||||
|
||||
String pattern = cDateTimeInstanceCache.get(key);
|
||||
if (pattern == null) {
|
||||
try {
|
||||
DateFormat formatter;
|
||||
if (dateStyle == null) {
|
||||
formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale);
|
||||
} else if (timeStyle == null) {
|
||||
formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale);
|
||||
} else {
|
||||
formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale);
|
||||
}
|
||||
pattern = ((SimpleDateFormat)formatter).toPattern();
|
||||
final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
|
||||
if (previous != null) {
|
||||
// even though it doesn't matter if another thread put the pattern
|
||||
// it's still good practice to return the String instance that is
|
||||
// actually in the ConcurrentMap
|
||||
pattern= previous;
|
||||
}
|
||||
} catch (final ClassCastException ex) {
|
||||
throw new IllegalArgumentException("No date time pattern for locale: " + locale);
|
||||
}
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Helper class to hold multi-part Map keys</p>
|
||||
*/
|
||||
private static class MultipartKey {
|
||||
private final Object[] keys;
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
|
||||
* @param keys the set of objects that make up the key. Each key may be null.
|
||||
*/
|
||||
MultipartKey(final Object... keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
// Eliminate the usual boilerplate because
|
||||
// this inner static class is only used in a generic ConcurrentHashMap
|
||||
// which will not compare against other Object types
|
||||
return Arrays.equals(keys, ((MultipartKey)obj).keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if(hashCode==0) {
|
||||
int rc= 0;
|
||||
for(final Object key : keys) {
|
||||
if(key!=null) {
|
||||
rc= rc*7 + key.hashCode();
|
||||
}
|
||||
}
|
||||
hashCode= rc;
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.tuple;
|
||||
|
||||
/**
|
||||
* <p>An immutable pair consisting of two {@code Object} elements.</p>
|
||||
*
|
||||
* <p>Although the implementation is immutable, there is no restriction on the objects
|
||||
* that may be stored. If mutable objects are stored in the pair, then the pair
|
||||
* itself effectively becomes mutable. The class is also {@code final}, so a subclass
|
||||
* can not add undesirable behaviour.</p>
|
||||
*
|
||||
* <p>#ThreadSafe# if both paired objects are thread-safe</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
*
|
||||
* @since Lang 3.0
|
||||
*/
|
||||
public final class ImmutablePair<L, R> extends Pair<L, R> {
|
||||
|
||||
/**
|
||||
* An immutable pair of nulls.
|
||||
*/
|
||||
// This is not defined with generics to avoid warnings in call sites.
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final ImmutablePair NULL = ImmutablePair.of(null, null);
|
||||
|
||||
/** Serialization version */
|
||||
private static final long serialVersionUID = 4954918890077093841L;
|
||||
|
||||
/**
|
||||
* Returns an immutable pair of nulls.
|
||||
*
|
||||
* @param <L> the left element of this pair. Value is {@code null}.
|
||||
* @param <R> the right element of this pair. Value is {@code null}.
|
||||
* @return an immutable pair of nulls.
|
||||
* @since 3.6
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <L, R> ImmutablePair<L, R> nullPair() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Left object */
|
||||
public final L left;
|
||||
/** Right object */
|
||||
public final R right;
|
||||
|
||||
/**
|
||||
* <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
|
||||
*
|
||||
* <p>This factory allows the pair to be created using inference to
|
||||
* obtain the generic types.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
* @param left the left element, may be null
|
||||
* @param right the right element, may be null
|
||||
* @return a pair formed from the two parameters, not null
|
||||
*/
|
||||
public static <L, R> ImmutablePair<L, R> of(final L left, final R right) {
|
||||
return new ImmutablePair<>(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pair instance.
|
||||
*
|
||||
* @param left the left value, may be null
|
||||
* @param right the right value, may be null
|
||||
*/
|
||||
public ImmutablePair(final L left, final R right) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Throws {@code UnsupportedOperationException}.</p>
|
||||
*
|
||||
* <p>This pair is immutable, so this operation is not supported.</p>
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return never
|
||||
* @throws UnsupportedOperationException as this operation is not supported
|
||||
*/
|
||||
@Override
|
||||
public R setValue(final R value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.tuple;
|
||||
|
||||
/**
|
||||
* <p>An immutable triple consisting of three {@code Object} elements.</p>
|
||||
*
|
||||
* <p>Although the implementation is immutable, there is no restriction on the objects
|
||||
* that may be stored. If mutable objects are stored in the triple, then the triple
|
||||
* itself effectively becomes mutable. The class is also {@code final}, so a subclass
|
||||
* can not add undesirable behaviour.</p>
|
||||
*
|
||||
* <p>#ThreadSafe# if all three objects are thread-safe</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <M> the middle element type
|
||||
* @param <R> the right element type
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class ImmutableTriple<L, M, R> extends Triple<L, M, R> {
|
||||
|
||||
/**
|
||||
* An immutable triple of nulls.
|
||||
*/
|
||||
// This is not defined with generics to avoid warnings in call sites.
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final ImmutableTriple NULL = ImmutableTriple.of(null, null, null);
|
||||
|
||||
/** Serialization version */
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Returns an immutable triple of nulls.
|
||||
*
|
||||
* @param <L> the left element of this triple. Value is {@code null}.
|
||||
* @param <M> the middle element of this triple. Value is {@code null}.
|
||||
* @param <R> the right element of this triple. Value is {@code null}.
|
||||
* @return an immutable triple of nulls.
|
||||
* @since 3.6
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <L, M, R> ImmutableTriple<L, M, R> nullTriple() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Left object */
|
||||
public final L left;
|
||||
/** Middle object */
|
||||
public final M middle;
|
||||
/** Right object */
|
||||
public final R right;
|
||||
|
||||
/**
|
||||
* <p>Obtains an immutable triple of from three objects inferring the generic types.</p>
|
||||
*
|
||||
* <p>This factory allows the triple to be created using inference to
|
||||
* obtain the generic types.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <M> the middle element type
|
||||
* @param <R> the right element type
|
||||
* @param left the left element, may be null
|
||||
* @param middle the middle element, may be null
|
||||
* @param right the right element, may be null
|
||||
* @return a triple formed from the three parameters, not null
|
||||
*/
|
||||
public static <L, M, R> ImmutableTriple<L, M, R> of(final L left, final M middle, final R right) {
|
||||
return new ImmutableTriple<>(left, middle, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new triple instance.
|
||||
*
|
||||
* @param left the left value, may be null
|
||||
* @param middle the middle value, may be null
|
||||
* @param right the right value, may be null
|
||||
*/
|
||||
public ImmutableTriple(final L left, final M middle, final R right) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.middle = middle;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public M getMiddle() {
|
||||
return middle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
}
|
||||
|
@ -1,178 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.tuple;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.builder.CompareToBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>A pair consisting of two elements.</p>
|
||||
*
|
||||
* <p>This class is an abstract implementation defining the basic API.
|
||||
* It refers to the elements as 'left' and 'right'. It also implements the
|
||||
* {@code Map.Entry} interface where the key is 'left' and the value is 'right'.</p>
|
||||
*
|
||||
* <p>Subclass implementations may be mutable or immutable.
|
||||
* However, there is no restriction on the type of the stored objects that may be stored.
|
||||
* If mutable objects are stored in the pair, then the pair itself effectively becomes mutable.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
*
|
||||
* @since Lang 3.0
|
||||
*/
|
||||
public abstract class Pair<L, R> implements Map.Entry<L, R>, Comparable<Pair<L, R>>, Serializable {
|
||||
|
||||
/** Serialization version */
|
||||
private static final long serialVersionUID = 4954918890077093841L;
|
||||
|
||||
/**
|
||||
* <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
|
||||
*
|
||||
* <p>This factory allows the pair to be created using inference to
|
||||
* obtain the generic types.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
* @param left the left element, may be null
|
||||
* @param right the right element, may be null
|
||||
* @return a pair formed from the two parameters, not null
|
||||
*/
|
||||
public static <L, R> Pair<L, R> of(final L left, final R right) {
|
||||
return new ImmutablePair<>(left, right);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the left element from this pair.</p>
|
||||
*
|
||||
* <p>When treated as a key-value pair, this is the key.</p>
|
||||
*
|
||||
* @return the left element, may be null
|
||||
*/
|
||||
public abstract L getLeft();
|
||||
|
||||
/**
|
||||
* <p>Gets the right element from this pair.</p>
|
||||
*
|
||||
* <p>When treated as a key-value pair, this is the value.</p>
|
||||
*
|
||||
* @return the right element, may be null
|
||||
*/
|
||||
public abstract R getRight();
|
||||
|
||||
/**
|
||||
* <p>Gets the key from this pair.</p>
|
||||
*
|
||||
* <p>This method implements the {@code Map.Entry} interface returning the
|
||||
* left element as the key.</p>
|
||||
*
|
||||
* @return the left element as the key, may be null
|
||||
*/
|
||||
@Override
|
||||
public final L getKey() {
|
||||
return getLeft();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the value from this pair.</p>
|
||||
*
|
||||
* <p>This method implements the {@code Map.Entry} interface returning the
|
||||
* right element as the value.</p>
|
||||
*
|
||||
* @return the right element as the value, may be null
|
||||
*/
|
||||
@Override
|
||||
public R getValue() {
|
||||
return getRight();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compares the pair based on the left element followed by the right element.
|
||||
* The types must be {@code Comparable}.</p>
|
||||
*
|
||||
* @param other the other pair, not null
|
||||
* @return negative if this is less, zero if equal, positive if greater
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(final Pair<L, R> other) {
|
||||
return new CompareToBuilder().append(getLeft(), other.getLeft())
|
||||
.append(getRight(), other.getRight()).toComparison();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Compares this pair to another based on the two elements.</p>
|
||||
*
|
||||
* @param obj the object to compare to, null returns false
|
||||
* @return true if the elements of the pair are equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof Map.Entry<?, ?>) {
|
||||
final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
|
||||
return Objects.equals(getKey(), other.getKey())
|
||||
&& Objects.equals(getValue(), other.getValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a suitable hash code.
|
||||
* The hash code follows the definition in {@code Map.Entry}.</p>
|
||||
*
|
||||
* @return the hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// see Map.Entry API specification
|
||||
return (getKey() == null ? 0 : getKey().hashCode()) ^
|
||||
(getValue() == null ? 0 : getValue().hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a String representation of this pair using the format {@code ($left,$right)}.</p>
|
||||
*
|
||||
* @return a string describing this object, not null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder().append('(').append(getLeft()).append(',').append(getRight()).append(')').toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats the receiver using the given format.</p>
|
||||
*
|
||||
* <p>This uses {@link java.util.Formattable} to perform the formatting. Two variables may
|
||||
* be used to embed the left and right elements. Use {@code %1$s} for the left
|
||||
* element (key) and {@code %2$s} for the right element (value).
|
||||
* The default format used by {@code toString()} is {@code (%1$s,%2$s)}.</p>
|
||||
*
|
||||
* @param format the format string, optionally containing {@code %1$s} and {@code %2$s}, not null
|
||||
* @return the formatted string, not null
|
||||
*/
|
||||
public String toString(final String format) {
|
||||
return String.format(format, getLeft(), getRight());
|
||||
}
|
||||
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
* 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 com.qihoo360.replugin.ext.lang3.tuple;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.builder.CompareToBuilder;
|
||||
|
||||
/**
|
||||
* <p>A triple consisting of three elements.</p>
|
||||
*
|
||||
* <p>This class is an abstract implementation defining the basic API.
|
||||
* It refers to the elements as 'left', 'middle' and 'right'.</p>
|
||||
*
|
||||
* <p>Subclass implementations may be mutable or immutable.
|
||||
* However, there is no restriction on the type of the stored objects that may be stored.
|
||||
* If mutable objects are stored in the triple, then the triple itself effectively becomes mutable.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <M> the middle element type
|
||||
* @param <R> the right element type
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class Triple<L, M, R> implements Comparable<Triple<L, M, R>>, Serializable {
|
||||
|
||||
/** Serialization version */
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <p>Obtains an immutable triple of from three objects inferring the generic types.</p>
|
||||
*
|
||||
* <p>This factory allows the triple to be created using inference to
|
||||
* obtain the generic types.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <M> the middle element type
|
||||
* @param <R> the right element type
|
||||
* @param left the left element, may be null
|
||||
* @param middle the middle element, may be null
|
||||
* @param right the right element, may be null
|
||||
* @return a triple formed from the three parameters, not null
|
||||
*/
|
||||
public static <L, M, R> Triple<L, M, R> of(final L left, final M middle, final R right) {
|
||||
return new ImmutableTriple<>(left, middle, right);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the left element from this triple.</p>
|
||||
*
|
||||
* @return the left element, may be null
|
||||
*/
|
||||
public abstract L getLeft();
|
||||
|
||||
/**
|
||||
* <p>Gets the middle element from this triple.</p>
|
||||
*
|
||||
* @return the middle element, may be null
|
||||
*/
|
||||
public abstract M getMiddle();
|
||||
|
||||
/**
|
||||
* <p>Gets the right element from this triple.</p>
|
||||
*
|
||||
* @return the right element, may be null
|
||||
*/
|
||||
public abstract R getRight();
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compares the triple based on the left element, followed by the middle element,
|
||||
* finally the right element.
|
||||
* The types must be {@code Comparable}.</p>
|
||||
*
|
||||
* @param other the other triple, not null
|
||||
* @return negative if this is less, zero if equal, positive if greater
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(final Triple<L, M, R> other) {
|
||||
return new CompareToBuilder().append(getLeft(), other.getLeft())
|
||||
.append(getMiddle(), other.getMiddle())
|
||||
.append(getRight(), other.getRight()).toComparison();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Compares this triple to another based on the three elements.</p>
|
||||
*
|
||||
* @param obj the object to compare to, null returns false
|
||||
* @return true if the elements of the triple are equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof Triple<?, ?, ?>) {
|
||||
final Triple<?, ?, ?> other = (Triple<?, ?, ?>) obj;
|
||||
return Objects.equals(getLeft(), other.getLeft())
|
||||
&& Objects.equals(getMiddle(), other.getMiddle())
|
||||
&& Objects.equals(getRight(), other.getRight());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a suitable hash code.</p>
|
||||
*
|
||||
* @return the hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (getLeft() == null ? 0 : getLeft().hashCode()) ^
|
||||
(getMiddle() == null ? 0 : getMiddle().hashCode()) ^
|
||||
(getRight() == null ? 0 : getRight().hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a String representation of this triple using the format {@code ($left,$middle,$right)}.</p>
|
||||
*
|
||||
* @return a string describing this object, not null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + getLeft() + "," + getMiddle() + "," + getRight() + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats the receiver using the given format.</p>
|
||||
*
|
||||
* <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
|
||||
* be used to embed the left and right elements. Use {@code %1$s} for the left
|
||||
* element, {@code %2$s} for the middle and {@code %3$s} for the right element.
|
||||
* The default format used by {@code toString()} is {@code (%1$s,%2$s,%3$s)}.</p>
|
||||
*
|
||||
* @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null
|
||||
* @return the formatted string, not null
|
||||
*/
|
||||
public String toString(final String format) {
|
||||
return String.format(format, getLeft(), getMiddle(), getRight());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,8 +16,7 @@
|
||||
|
||||
package com.qihoo360.replugin.helper;
|
||||
|
||||
import com.qihoo360.replugin.ext.lang3.ClassUtils;
|
||||
import com.qihoo360.replugin.ext.lang3.reflect.FieldUtils;
|
||||
import com.qihoo360.replugin.utils.ReflectUtils;
|
||||
|
||||
/**
|
||||
* 从宿主的 RePluginHostConfig 中获取一些字段值,
|
||||
@ -69,89 +68,89 @@ public class HostConfigHelper {
|
||||
static {
|
||||
|
||||
try {
|
||||
HOST_CONFIG_CLASS = ClassUtils.getClass(HOST_CONFIG_FILE_PATH + HOST_CONFIG_FILE_NAME);
|
||||
HOST_CONFIG_CLASS = ReflectUtils.getClass(HOST_CONFIG_FILE_PATH + HOST_CONFIG_FILE_NAME);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_USE_APPCOMPAT = readField("ACTIVITY_PIT_USE_APPCOMPAT");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_TS_STANDARD = readField("ACTIVITY_PIT_COUNT_TS_STANDARD");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TOP");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TASK");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_NTS_STANDARD = readField("ACTIVITY_PIT_COUNT_NTS_STANDARD");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ACTIVITY_PIT_COUNT_TASK = readField("ACTIVITY_PIT_COUNT_TASK");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ADAPTER_COMPATIBLE_VERSION = readField("ADAPTER_COMPATIBLE_VERSION");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
|
||||
try {
|
||||
ADAPTER_CURRENT_VERSION = readField("ADAPTER_CURRENT_VERSION");
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore, Just use default value
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T readField(String name) {
|
||||
private static <T> T readField(String name) throws NoSuchFieldException {
|
||||
try {
|
||||
// 就是要强转
|
||||
//noinspection unchecked
|
||||
return (T) FieldUtils.readStaticField(HOST_CONFIG_CLASS, name, true);
|
||||
return (T) ReflectUtils.readStaticField(HOST_CONFIG_CLASS, name);
|
||||
} catch (IllegalAccessException e) {
|
||||
// 此Field可能为非Public权限,不过由于我们做了Accessible处理,可能性非常低
|
||||
// NOTE 因为类型转换发生在readField返回值之后才做,故“ClassCastException”只会出现在static方法块内
|
||||
@ -159,7 +158,7 @@ public class HostConfigHelper {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
// NOTE 不需要Catch IllegalArgumentException,因为只要此Field找不到就抛,符合预期
|
||||
// NOTE 不需要Catch NoSuchFieldException,因为只要此Field找不到就抛,符合预期
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
|
Loading…
Reference in New Issue
Block a user