Last active
April 24, 2024 02:45
-
-
Save WinterAlexander/5ec4bc65de744fdd90354013e5c0c0d7 to your computer and use it in GitHub Desktop.
NoBleedingFreeTypeFontGenerator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.greenfrisbee.makerking.render.font; | |
| import com.badlogic.gdx.Gdx; | |
| import com.badlogic.gdx.files.FileHandle; | |
| import com.badlogic.gdx.graphics.Color; | |
| import com.badlogic.gdx.graphics.Pixmap; | |
| import com.badlogic.gdx.graphics.g2d.BitmapFont; | |
| import com.badlogic.gdx.graphics.g2d.PixmapPacker; | |
| import com.badlogic.gdx.graphics.g2d.freetype.FreeType; | |
| import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; | |
| import com.badlogic.gdx.math.Rectangle; | |
| import com.badlogic.gdx.utils.GdxRuntimeException; | |
| import com.badlogic.gdx.utils.Null; | |
| import com.winteralexander.gdx.utils.ReflectionUtil; | |
| import java.nio.ByteBuffer; | |
| /** | |
| * {@link FreeTypeFontGenerator} that fixes bleeding for fonts with shadows | |
| * <p> | |
| * Created on 2024-04-23. | |
| * | |
| * @author Alexander Winter | |
| */ | |
| public class NoBleedingFreeTypeFontGenerator extends FreeTypeFontGenerator { | |
| private final FreeType.Face privateFace; | |
| private final boolean privateBitmapped; | |
| public NoBleedingFreeTypeFontGenerator(FileHandle fontFile) { | |
| super(fontFile); | |
| privateFace = ReflectionUtil.get(this, "face"); | |
| privateBitmapped = ReflectionUtil.get(this, "bitmapped"); | |
| } | |
| public NoBleedingFreeTypeFontGenerator(FileHandle fontFile, int faceIndex) { | |
| super(fontFile, faceIndex); | |
| privateFace = ReflectionUtil.get(this, "face"); | |
| privateBitmapped = ReflectionUtil.get(this, "bitmapped"); | |
| } | |
| @Override | |
| protected @Null BitmapFont.Glyph createGlyph(char c, | |
| FreeTypeBitmapFontData data, | |
| FreeTypeFontParameter parameter, | |
| FreeType.Stroker stroker, | |
| float baseLine, | |
| PixmapPacker packer) { | |
| boolean missing = privateFace.getCharIndex(c) == 0 && c != 0; | |
| if(missing) | |
| return null; | |
| if(!privateFace.loadChar(c, getLoadingFlags(parameter))) return null; | |
| FreeType.GlyphSlot slot = privateFace.getGlyph(); | |
| FreeType.Glyph mainGlyph = slot.getGlyph(); | |
| try { | |
| mainGlyph.toBitmap(parameter.mono ? FreeType.FT_RENDER_MODE_MONO : FreeType.FT_RENDER_MODE_NORMAL); | |
| } catch (GdxRuntimeException e) { | |
| mainGlyph.dispose(); | |
| Gdx.app.log("FreeTypeFontGenerator", "Couldn't render char: " + c); | |
| return null; | |
| } | |
| FreeType.Bitmap mainBitmap = mainGlyph.getBitmap(); | |
| Pixmap mainPixmap = mainBitmap.getPixmap(Pixmap.Format.RGBA8888, parameter.color, parameter.gamma); | |
| if (mainBitmap.getWidth() != 0 && mainBitmap.getRows() != 0) { | |
| int offsetX = 0, offsetY = 0; | |
| if (parameter.borderWidth > 0) { | |
| // execute stroker; this generates a glyph "extended" along the outline | |
| int top = mainGlyph.getTop(), left = mainGlyph.getLeft(); | |
| FreeType.Glyph borderGlyph = slot.getGlyph(); | |
| borderGlyph.strokeBorder(stroker, false); | |
| borderGlyph.toBitmap(parameter.mono ? FreeType.FT_RENDER_MODE_MONO : FreeType.FT_RENDER_MODE_NORMAL); | |
| offsetX = left - borderGlyph.getLeft(); | |
| offsetY = -(top - borderGlyph.getTop()); | |
| // Render border (pixmap is bigger than main). | |
| FreeType.Bitmap borderBitmap = borderGlyph.getBitmap(); | |
| Pixmap borderPixmap = borderBitmap.getPixmap(Pixmap.Format.RGBA8888, parameter.borderColor, parameter.borderGamma); | |
| // Draw main glyph on top of border. | |
| for (int i = 0, n = parameter.renderCount; i < n; i++) | |
| borderPixmap.drawPixmap(mainPixmap, offsetX, offsetY); | |
| mainPixmap.dispose(); | |
| mainGlyph.dispose(); | |
| mainPixmap = borderPixmap; | |
| mainGlyph = borderGlyph; | |
| } | |
| if (parameter.shadowOffsetX != 0 || parameter.shadowOffsetY != 0) { | |
| int mainW = mainPixmap.getWidth(), mainH = mainPixmap.getHeight(); | |
| int shadowOffsetX = Math.max(parameter.shadowOffsetX, 0), shadowOffsetY = Math.max(parameter.shadowOffsetY, 0); | |
| int shadowW = mainW + Math.abs(parameter.shadowOffsetX), shadowH = mainH + Math.abs(parameter.shadowOffsetY); | |
| Pixmap shadowPixmap = new Pixmap(shadowW, shadowH, mainPixmap.getFormat()); | |
| shadowPixmap.setColor(packer.getTransparentColor()); | |
| shadowPixmap.fill(); | |
| Color shadowColor = parameter.shadowColor; | |
| float a = shadowColor.a; | |
| if (a != 0) { | |
| byte r = (byte)(shadowColor.r * 255), g = (byte)(shadowColor.g * 255), b = (byte)(shadowColor.b * 255); | |
| ByteBuffer mainPixels = mainPixmap.getPixels(); | |
| ByteBuffer shadowPixels = shadowPixmap.getPixels(); | |
| for (int y = 0; y < mainH; y++) { | |
| int shadowRow = shadowW * (y + shadowOffsetY) + shadowOffsetX; | |
| for (int x = 0; x < mainW; x++) { | |
| int mainPixel = (mainW * y + x) * 4; | |
| byte mainA = mainPixels.get(mainPixel + 3); | |
| if (mainA == 0) { | |
| continue; | |
| } | |
| int shadowPixel = (shadowRow + x) * 4; | |
| shadowPixels.put(shadowPixel, r); | |
| shadowPixels.put(shadowPixel + 1, g); | |
| shadowPixels.put(shadowPixel + 2, b); | |
| shadowPixels.put(shadowPixel + 3, (byte)((mainA & 0xff) * a)); | |
| } | |
| } | |
| } | |
| // Draw main glyph (with any border) on top of shadow. | |
| for (int i = 0, n = parameter.renderCount; i < n; i++) | |
| shadowPixmap.drawPixmap(mainPixmap, Math.max(-parameter.shadowOffsetX, 0), Math.max(-parameter.shadowOffsetY, 0)); | |
| mainPixmap.dispose(); | |
| mainPixmap = shadowPixmap; | |
| } else if (parameter.borderWidth == 0) { | |
| // No shadow and no border, draw glyph additional times. | |
| for (int i = 0, n = parameter.renderCount - 1; i < n; i++) | |
| mainPixmap.drawPixmap(mainPixmap, 0, 0); | |
| } | |
| if (parameter.padTop > 0 || parameter.padLeft > 0 || parameter.padBottom > 0 || parameter.padRight > 0) { | |
| Pixmap padPixmap = new Pixmap(mainPixmap.getWidth() + parameter.padLeft + parameter.padRight, | |
| mainPixmap.getHeight() + parameter.padTop + parameter.padBottom, mainPixmap.getFormat()); | |
| padPixmap.setBlending(Pixmap.Blending.None); | |
| padPixmap.drawPixmap(mainPixmap, parameter.padLeft, parameter.padTop); | |
| mainPixmap.dispose(); | |
| mainPixmap = padPixmap; | |
| } | |
| } | |
| FreeType.GlyphMetrics metrics = slot.getMetrics(); | |
| BitmapFont.Glyph glyph = new BitmapFont.Glyph(); | |
| glyph.id = c; | |
| glyph.width = mainPixmap.getWidth(); | |
| glyph.height = mainPixmap.getHeight(); | |
| glyph.xoffset = mainGlyph.getLeft(); | |
| if (parameter.flip) | |
| glyph.yoffset = -mainGlyph.getTop() + (int)baseLine; | |
| else | |
| glyph.yoffset = -(glyph.height - mainGlyph.getTop()) - (int)baseLine; | |
| glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()) + (int)parameter.borderWidth + parameter.spaceX; | |
| if (privateBitmapped) { | |
| mainPixmap.setColor(Color.CLEAR); | |
| mainPixmap.fill(); | |
| ByteBuffer buf = mainBitmap.getBuffer(); | |
| int whiteIntBits = Color.WHITE.toIntBits(); | |
| int clearIntBits = Color.CLEAR.toIntBits(); | |
| for (int h = 0; h < glyph.height; h++) { | |
| int idx = h * mainBitmap.getPitch(); | |
| for (int w = 0; w < (glyph.width + glyph.xoffset); w++) { | |
| int bit = (buf.get(idx + (w / 8)) >>> (7 - (w % 8))) & 1; | |
| mainPixmap.drawPixel(w, h, ((bit == 1) ? whiteIntBits : clearIntBits)); | |
| } | |
| } | |
| } | |
| Rectangle rect = packer.pack(mainPixmap); | |
| glyph.page = packer.getPages().size - 1; // Glyph is always packed into the last page for now. | |
| glyph.srcX = (int)rect.x; | |
| glyph.srcY = (int)rect.y; | |
| // If a page was added, create a new texture region for the incrementally added glyph. | |
| if (parameter.incremental && data.regions != null && data.regions.size <= glyph.page) | |
| packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); | |
| mainPixmap.dispose(); | |
| mainGlyph.dispose(); | |
| return glyph; | |
| } | |
| private int getLoadingFlags (FreeTypeFontParameter parameter) { | |
| int loadingFlags = FreeType.FT_LOAD_DEFAULT; | |
| switch (parameter.hinting) { | |
| case None: | |
| loadingFlags |= FreeType.FT_LOAD_NO_HINTING; | |
| break; | |
| case Slight: | |
| loadingFlags |= FreeType.FT_LOAD_TARGET_LIGHT; | |
| break; | |
| case Medium: | |
| loadingFlags |= FreeType.FT_LOAD_TARGET_NORMAL; | |
| break; | |
| case Full: | |
| loadingFlags |= FreeType.FT_LOAD_TARGET_MONO; | |
| break; | |
| case AutoSlight: | |
| loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_LIGHT; | |
| break; | |
| case AutoMedium: | |
| loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_NORMAL; | |
| break; | |
| case AutoFull: | |
| loadingFlags |= FreeType.FT_LOAD_FORCE_AUTOHINT | FreeType.FT_LOAD_TARGET_MONO; | |
| break; | |
| } | |
| return loadingFlags; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.greenfrisbee.makerking.render.font; | |
| import com.badlogic.gdx.assets.AssetDescriptor; | |
| import com.badlogic.gdx.assets.AssetLoaderParameters; | |
| import com.badlogic.gdx.assets.AssetManager; | |
| import com.badlogic.gdx.assets.loaders.FileHandleResolver; | |
| import com.badlogic.gdx.assets.loaders.SynchronousAssetLoader; | |
| import com.badlogic.gdx.files.FileHandle; | |
| import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; | |
| import com.badlogic.gdx.utils.Array; | |
| import static com.greenfrisbee.makerking.render.font.NoBleedingFreeTypeFontGeneratorLoader.*; | |
| /** | |
| * Loads {@link NoBleedingFreeTypeFontGenerator} from TTF files | |
| * <p> | |
| * Created on 2024-04-23. | |
| * | |
| * @author Alexander Winter | |
| */ | |
| public class NoBleedingFreeTypeFontGeneratorLoader | |
| extends SynchronousAssetLoader<FreeTypeFontGenerator, FreeTypeFontGeneratorParameters> { | |
| public NoBleedingFreeTypeFontGeneratorLoader (FileHandleResolver resolver) { | |
| super(resolver); | |
| } | |
| @Override | |
| public FreeTypeFontGenerator load(AssetManager assetManager, | |
| String fileName, | |
| FileHandle file, | |
| FreeTypeFontGeneratorParameters parameter) { | |
| if (file.extension().equals("gen")) | |
| return new NoBleedingFreeTypeFontGenerator(file.sibling(file.nameWithoutExtension())); | |
| else | |
| return new NoBleedingFreeTypeFontGenerator(file); | |
| } | |
| @SuppressWarnings("rawtypes") | |
| @Override | |
| public Array<AssetDescriptor> getDependencies(String fileName, | |
| FileHandle file, | |
| FreeTypeFontGeneratorParameters parameter) { | |
| return null; | |
| } | |
| static public class FreeTypeFontGeneratorParameters extends AssetLoaderParameters<FreeTypeFontGenerator> { | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.winteralexander.gdx.utils; | |
| import com.badlogic.gdx.utils.Array; | |
| import java.lang.reflect.*; | |
| import static com.winteralexander.gdx.utils.TypeUtil.isPrimitiveBox; | |
| /** | |
| * Utility class to use reflection on objects | |
| * <p> | |
| * Created on 2018-02-09. | |
| * | |
| * @author Alexander Winter | |
| */ | |
| @SuppressWarnings("deprecation") | |
| public class ReflectionUtil { | |
| private ReflectionUtil() {} | |
| /** | |
| * Swap all fields value for 2 objects | |
| * | |
| * @param o1 object 1 | |
| * @param o2 object 2 | |
| */ | |
| public static void swapFields(Object o1, Object o2) { | |
| if(o1 == null || o2 == null) | |
| throw new IllegalArgumentException("Objects must not be null"); | |
| if(!o1.getClass().equals(o2.getClass())) | |
| throw new IllegalArgumentException("Objects are not the same type"); | |
| Class<?> type = o1.getClass(); | |
| while(type != null) { | |
| for(Field field : type.getDeclaredFields()) { | |
| if(Modifier.isStatic(field.getModifiers())) | |
| continue; | |
| if(!field.isAccessible()) | |
| field.setAccessible(true); | |
| try { | |
| Object tmp = field.get(o1); | |
| field.set(o1, field.get(o2)); | |
| field.set(o2, tmp); | |
| } catch(IllegalAccessException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| } | |
| type = type.getSuperclass(); | |
| } | |
| } | |
| /** | |
| * Clones an object into another | |
| * | |
| * @param source source object | |
| * @param destination destination object | |
| */ | |
| public static void copy(Object source, Object destination) { | |
| Validation.ensureNotNull(source, "source"); | |
| Validation.ensureNotNull(destination, "destination"); | |
| if(!source.getClass().equals(destination.getClass())) | |
| throw new IllegalArgumentException("Objects are not the same type"); | |
| Class<?> type = source.getClass(); | |
| while(type != null) { | |
| for(Field field : type.getDeclaredFields()) { | |
| if(Modifier.isStatic(field.getModifiers())) | |
| continue; | |
| if(!field.isAccessible()) | |
| field.setAccessible(true); | |
| try { | |
| field.set(destination, field.get(source)); | |
| } catch(IllegalAccessException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| } | |
| type = type.getSuperclass(); | |
| } | |
| } | |
| /** | |
| * Sets the field of an object to a specified value | |
| * | |
| * @param object object to edit field's of | |
| * @param field field to edit | |
| * @param value value to set | |
| */ | |
| public static void set(Object object, String field, Object value) { | |
| Validation.ensureNotNull(object, "object"); | |
| set(object.getClass(), object, field, value); | |
| } | |
| /** | |
| * Sets the field of an object to a specified value | |
| * | |
| * @param type type of object to set value of | |
| * @param object object to edit field's of, or null if static field | |
| * @param field field to edit | |
| * @param value value to set | |
| */ | |
| public static void set(Class<?> type, Object object, String field, Object value) { | |
| Validation.ensureNotNull(type, "type"); | |
| Validation.ensureNotNull(field, "field"); | |
| Validation.ensureNotNull(value, "value"); | |
| while(type != null) { | |
| try { | |
| Field fieldHandle = type.getDeclaredField(field); | |
| fieldHandle.setAccessible(true); | |
| fieldHandle.set(object, value); | |
| return; | |
| } catch(IllegalAccessException ex) { | |
| throw new RuntimeException(ex); | |
| } catch(NoSuchFieldException ignored) {} | |
| type = type.getSuperclass(); | |
| } | |
| throw new RuntimeException("Field not found"); | |
| } | |
| @SuppressWarnings("unchecked") | |
| public static <T> T getStatic(Class<?> type, String field) { | |
| return (T)getStatic(type, field, Object.class); | |
| } | |
| public static <T> T getStatic(Class<?> type, String field, Class<T> returnType) { | |
| Validation.ensureNotNull(type, "type"); | |
| Validation.ensureNotNull(field, "field"); | |
| Validation.ensureNotNull(returnType, "returnType"); | |
| while(type != null) { | |
| try { | |
| Field fieldHandle = type.getDeclaredField(field); | |
| if(!fieldHandle.isAccessible()) | |
| fieldHandle.setAccessible(true); | |
| return returnType.cast(fieldHandle.get(null)); | |
| } catch(IllegalAccessException ex) { | |
| throw new RuntimeException(ex); | |
| } catch(NoSuchFieldException ignored) {} | |
| type = type.getSuperclass(); | |
| } | |
| throw new RuntimeException("Field " + field + " not found for type " + type); | |
| } | |
| @SuppressWarnings("unchecked") | |
| public static <T> T get(Object object, String field) { | |
| return (T)get(object, field, Object.class); | |
| } | |
| /** | |
| * Gets the field of an object for a specified field name | |
| * | |
| * @param object object to edit field's of | |
| * @param field field to edit | |
| * @param type type of field | |
| */ | |
| public static <T> T get(Object object, String field, Class<T> type) { | |
| Validation.ensureNotNull(object, "object"); | |
| Validation.ensureNotNull(field, "field"); | |
| Validation.ensureNotNull(type, "type"); | |
| Class<?> t = object.getClass(); | |
| while(t != null) { | |
| try { | |
| Field fieldHandle = t.getDeclaredField(field); | |
| if(!fieldHandle.isAccessible()) | |
| fieldHandle.setAccessible(true); | |
| return type.cast(fieldHandle.get(object)); | |
| } catch(IllegalAccessException ex) { | |
| throw new RuntimeException(ex); | |
| } catch(NoSuchFieldException ignored) {} | |
| t = t.getSuperclass(); | |
| } | |
| throw new RuntimeException("Field " + field + " not found for type " + object.getClass()); | |
| } | |
| public static boolean has(Class<?> type, String field) { | |
| Validation.ensureNotNull(type, "type"); | |
| Validation.ensureNotNull(field, "field"); | |
| Class<?> t = type; | |
| while(t != null) { | |
| try { | |
| t.getDeclaredField(field); | |
| return true; | |
| } catch(NoSuchFieldException ignored) {} | |
| t = t.getSuperclass(); | |
| } | |
| return false; | |
| } | |
| public static Class<?> getType(Class<?> type, String field) { | |
| Validation.ensureNotNull(type, "type"); | |
| Validation.ensureNotNull(field, "field"); | |
| Class<?> t = type; | |
| while(t != null) { | |
| try { | |
| return t.getDeclaredField(field).getType(); | |
| } catch(NoSuchFieldException ignored) {} | |
| t = t.getSuperclass(); | |
| } | |
| throw new RuntimeException("Field " + field + " not found for type " + type); | |
| } | |
| @SuppressWarnings({"unchecked", "StringEquality"}) | |
| public static <T> T callStatic(Class<?> type, String method, Object... params) { | |
| Validation.ensureNotNull(method, "method"); | |
| method = method.intern(); | |
| while(type != null) { | |
| try { | |
| for(Method methodHandle : type.getDeclaredMethods()) { | |
| if(methodHandle.getName() != method) | |
| continue; | |
| if(methodHandle.getParameterCount() != params.length) | |
| continue; | |
| if(!methodHandle.isAccessible()) | |
| methodHandle.setAccessible(true); | |
| try { | |
| return (T)methodHandle.invoke(null, params); | |
| } catch(IllegalArgumentException ignored) {} | |
| } | |
| } catch(IllegalAccessException | InvocationTargetException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| type = type.getSuperclass(); | |
| } | |
| throw new RuntimeException("Method " + method + " not found for type " + type); | |
| } | |
| @SuppressWarnings({"unchecked", "StringEquality"}) | |
| public static <T> T call(Object object, String method, Object... params) { | |
| Validation.ensureNotNull(object, "object"); | |
| Validation.ensureNotNull(method, "method"); | |
| method = method.intern(); | |
| Class<?> t = object.getClass(); | |
| while(t != null) { | |
| try { | |
| for(Method methodHandle : t.getDeclaredMethods()) { | |
| if(methodHandle.getName() != method) | |
| continue; | |
| if(methodHandle.getParameterCount() != params.length) | |
| continue; | |
| if(!methodHandle.isAccessible()) | |
| methodHandle.setAccessible(true); | |
| try { | |
| return (T)methodHandle.invoke(object, params); | |
| } catch(IllegalArgumentException ignored) {} | |
| } | |
| } catch(IllegalAccessException | InvocationTargetException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| t = t.getSuperclass(); | |
| } | |
| throw new RuntimeException("Method " + method + " not found for type " + object.getClass()); | |
| } | |
| @SuppressWarnings("unchecked") | |
| public static <T> T construct(Class<T> type, Object... params) { | |
| Validation.ensureNotNull(type, "type"); | |
| for(Constructor<?> constructor : type.getDeclaredConstructors()) { | |
| try { | |
| if(!constructor.isAccessible()) | |
| constructor.setAccessible(true); | |
| return (T)constructor.newInstance(params); | |
| } catch(InstantiationException | IllegalArgumentException | IllegalAccessException ignored) { | |
| // continue | |
| } catch(InvocationTargetException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| } | |
| throw new RuntimeException("No matching constructor found"); | |
| } | |
| public static String toPrettyString(Object object) { | |
| return toPrettyString(object, Integer.MAX_VALUE, 0, new Array<>()); | |
| } | |
| public static String toPrettyString(Object object, | |
| int maxDepth, | |
| int indentationLevel) { | |
| return toPrettyString(object, maxDepth, indentationLevel, new Array<>()); | |
| } | |
| private static String toPrettyString(Object object, | |
| int maxDepth, | |
| int indentationLevel, | |
| Array<Object> objects) { | |
| objects.add(object); | |
| if(maxDepth <= 0) | |
| return object.toString() + '\n'; | |
| StringBuilder sb = new StringBuilder(); | |
| String newLine = System.lineSeparator(); | |
| Class<?> type = object.getClass(); | |
| sb.append(type.getSimpleName()) | |
| .append('@') | |
| .append(Integer.toHexString(object.hashCode())) | |
| .append(newLine); | |
| for(int i = 0; i < indentationLevel; i++) | |
| sb.append('\t'); | |
| if(type.isArray()) { | |
| if(!Object[].class.isAssignableFrom(type)) { | |
| int length = java.lang.reflect.Array.getLength(object); | |
| Object[] objArr = new Object[length]; | |
| for(int i = 0; i < length; i++) | |
| objArr[i] = java.lang.reflect.Array.get(object, i); | |
| object = objArr; | |
| } | |
| sb.append('[').append(newLine); | |
| int n = 0; | |
| for(Object obj : (Object[])object) { | |
| for(int i = 0; i < indentationLevel + 1; i++) | |
| sb.append('\t'); | |
| sb.append(n).append(": "); | |
| try { | |
| if(obj == null) | |
| sb.append("null").append(newLine); | |
| else if(obj.getClass().isAssignableFrom(String.class)) | |
| sb.append("\"").append(obj).append("\"").append(newLine); | |
| else if(obj.getClass().isPrimitive() | |
| || obj.getClass().isEnum() | |
| || isPrimitiveBox(obj.getClass())) | |
| sb.append(obj).append(newLine); | |
| else if(objects.contains(obj, true)) | |
| sb.append(obj.getClass().getSimpleName()) | |
| .append('@') | |
| .append(Integer.toHexString(obj.hashCode())) | |
| .append(newLine); | |
| else | |
| sb.append(toPrettyString(obj, maxDepth - 1, indentationLevel + 1, objects)); | |
| } catch(Throwable ex) { | |
| sb.append('(') | |
| .append(ex.getClass().getSimpleName()) | |
| .append(')') | |
| .append(newLine); | |
| } | |
| n++; | |
| } | |
| for(int i = 0; i < indentationLevel; i++) | |
| sb.append('\t'); | |
| sb.append(']').append(newLine); | |
| } else { | |
| sb.append('{').append(newLine); | |
| while(type != null) { | |
| for(Field field : type.getDeclaredFields()) { | |
| if(Modifier.isStatic(field.getModifiers())) | |
| continue; | |
| for(int i = 0; i < indentationLevel + 1; i++) | |
| sb.append('\t'); | |
| if(!field.isAccessible()) | |
| field.setAccessible(true); | |
| try { | |
| sb.append(field.getName()).append(": "); | |
| Object obj = field.get(object); | |
| if(obj == null) | |
| sb.append("null").append(newLine); | |
| else if(String.class.isAssignableFrom(field.getType())) | |
| sb.append("\"").append(obj).append("\"").append(newLine); | |
| else if(field.getType().isPrimitive() || field.getType().isEnum()) | |
| sb.append(obj).append(newLine); | |
| else if(objects.contains(obj, true)) | |
| sb.append(obj.getClass().getSimpleName()) | |
| .append('@') | |
| .append(Integer.toHexString(obj.hashCode())) | |
| .append(newLine); | |
| else { | |
| sb.append(toPrettyString(obj, maxDepth - 1, indentationLevel + 1, objects)); | |
| } | |
| } catch(Throwable ex) { | |
| sb.append("(").append(ex.getClass().getSimpleName()).append(")").append(newLine); | |
| ex.printStackTrace(); | |
| } | |
| } | |
| type = type.getSuperclass(); | |
| } | |
| for(int i = 0; i < indentationLevel; i++) | |
| sb.append('\t'); | |
| sb.append('}').append(newLine); | |
| } | |
| return sb.toString(); | |
| } | |
| @SuppressWarnings("raw") | |
| public static void disableAccessWarnings() { | |
| try { | |
| Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); | |
| Field field = unsafeClass.getDeclaredField("theUnsafe"); | |
| field.setAccessible(true); | |
| Object unsafe = field.get(null); | |
| Method putObjectVolatile = unsafeClass.getDeclaredMethod("putObjectVolatile", | |
| Object.class, long.class, Object.class); | |
| Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", | |
| Field.class); | |
| Class<?> loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger"); | |
| Field loggerField = loggerClass.getDeclaredField("logger"); | |
| Long offset = (Long)staticFieldOffset.invoke(unsafe, loggerField); | |
| putObjectVolatile.invoke(unsafe, loggerClass, offset, null); | |
| } catch(Exception ignored) {} | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment