/* * Copyright 2015 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.supercanvas.components; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.ArrayList; import ccre.cluck.Cluck; import ccre.log.Logger; import ccre.rconf.RConf; import ccre.rconf.RConf.Entry; import ccre.rconf.RConfable; import ccre.supercanvas.components.channels.RConfComponent; import ccre.supercanvas.components.pinned.CluckNetworkingComponent; /** * A RConfComponent that allows for some entire-PoultryInspector debugging * tools. * * @author skeggsc */ public class TopLevelRConfComponent extends RConfComponent { /** * An RConfable that allows inspection of Java objects. * * @author skeggsc */ public static class InspectionRConfable implements RConfable, Serializable { private static final long serialVersionUID = 9039257278527972486L; // transient so that we don't carry over anything. private transient final Object target; private transient final Class<?> as; private transient final boolean asList; private transient final TopLevelRConfComponent component; private InspectionRConfable(TopLevelRConfComponent component, Object target, Class<?> as, boolean possiblyAsList) { this.component = component; this.target = target; this.as = as; this.asList = possiblyAsList && (as.isArray() || target instanceof Iterable); } @SuppressWarnings("unused") public Entry[] queryRConf() throws InterruptedException { if (target == null) { return new Entry[] { RConf.title("Deleted"), RConf.string("cannot save inspections") }; } ArrayList<Entry> out = new ArrayList<Entry>(); String asStr = target.toString(); out.add(RConf.title("Inspecting: " + (asStr.length() > 30 ? asStr.substring(0, 27) + "..." : asStr))); if (asList) { addEntry(out, "as", "iterable/array"); addEntry(out, "as object", as); if (target instanceof Iterable) { int size = 0; for (Object entry : ((Iterable<?>) target)) { size++; } addEntry(out, "size", size); int i = 0; for (Object entry : ((Iterable<?>) target)) { addEntry(out, "[" + i + "]", entry); i++; } } else { int len = Array.getLength(target); addEntry(out, "length", len); for (int i = 0; i < len; i++) { addEntry(out, "[" + i + "]", Array.get(target, i)); } } } else { addEntry(out, "as", as); addEntry(out, "as super", as.getSuperclass()); addAsClass(out, as); } return out.toArray(new Entry[out.size()]); } private void addAsClass(ArrayList<Entry> out, Class<? extends Object> clazz) { for (Field f : clazz.getDeclaredFields()) { f.setAccessible(true); try { addEntry(out, clazz.getName() + "." + f.getName(), f.get(target)); } catch (IllegalAccessException e) { addEntry(out, clazz.getName() + "." + f.getName() + " failed", e); } } } private void addEntry(ArrayList<Entry> out, String key, Object value) { String valueStr = value == null ? "null" : value.toString(); int ml = Math.max(30, 60 - key.length()); out.add(RConf.button(key + ": " + (valueStr.length() > ml ? valueStr.substring(0, ml - 3) + "..." : valueStr))); } public boolean signalRConf(int field, byte[] data) throws InterruptedException { Object selected = null; if (field == 1) { selected = target.getClass(); } else if (field == 2 && as.getSuperclass() != null) { component.addInspection(target, asList ? as : as.getSuperclass(), false); return true; } else if (field >= 3) { if (asList) { if (field >= 4) { if (target instanceof Iterable) { int remaining = field - 4; for (Object o : (Iterable<?>) target) { if (remaining == 0) { selected = o; break; } else { remaining--; } } } else { selected = Array.get(target, field - 4); } } } else { selected = readAsClass(field - 3); } } if (selected != null) { component.addInspection(selected); return true; } return false; } private Object readAsClass(int field) { Field[] fields = as.getDeclaredFields(); if (field < fields.length) { Field f = fields[field]; try { f.setAccessible(true); return f.get(target); } catch (IllegalAccessException e) { Logger.warning("Could not access field " + f, e); } } return null; } } private static class TopLevelRConfable implements RConfable, Serializable { private static final long serialVersionUID = 7564831796084759163L; private TopLevelRConfComponent master; public Entry[] queryRConf() throws InterruptedException { return new Entry[] { RConf.title("Local Configuration"), RConf.button("Send Notify"), RConf.button("Inspect Root"), RConf.string("Use Deep Logging:"), RConf.fieldBoolean(CluckNetworkingComponent.useLoggingConnection) }; } public boolean signalRConf(int field, byte[] data) throws InterruptedException { if (field == 1) { Logger.fine("Notifying..."); Cluck.getNode().notifyNetworkModified(); Logger.fine("Notified!"); return true; } else if (field == 2) { master.addInspection(master.getPanel()); return true; } else if (field == 4 && data.length > 0) { CluckNetworkingComponent.useLoggingConnection = data[0] != 0; return true; } return false; } } private void addInspection(Object obj, Class<?> as, boolean possiblyAsList) { getPanel().add(new RConfComponent(getDragRelX(0), getDragRelY(0), "0x" + Integer.toHexString(System.identityHashCode(obj)), new InspectionRConfable(this, obj, as, possiblyAsList))); } private void addInspection(Object obj, Class<?> as) { addInspection(obj, as, true); } /** * Add an inspection pane for the given object to the SuperCanvasPanel. * * @param obj the object to inspect. */ public void addInspection(Object obj) { addInspection(obj, obj.getClass()); } /** * Create a new TopLevelRConfComponent at the specified location. * * @param cx the X coordinate. * @param cy the Y coordinate. */ public TopLevelRConfComponent(int cx, int cy) { super(cx, cy, "Local Configuration", new TopLevelRConfable()); ((TopLevelRConfable) device).master = this; } private static final long serialVersionUID = 3408977443946166053L; }