2013/01/18

As many people have noted, my Android application alogcat does not work correctly on Jellybean devices. The reason is that applications can no longer read log entries created by other applications. They can still read log entries created by themselves, but obviously that doesn’t help alogcat.

The logic makes sense I suppose. A poorly written application may log sensitive information. Allowing other applications to read this is a bad thing.

For alogcat, there is a workaround. You must explicitly grant alogcat the READ_LOGS permission from the command line. From the Android shell,

shell@android:/ $ pm grant org.jtb.alogcat android.permission.READ_LOGS    

Or if you have ADB installed, from your computer’s terminal with your device connected,

$ adb shell pm grant org.jtb.alogcat android.permission.READ_LOGS    

This of course requires that you install an Android terminal emulator, or have ADB installed on your computer. Android Terminal Emulator is a good choice for an Android terminal. The Android SDK, which includes ADB can be downloaded here.

Advertisements

Reflective toString()

2013/01/15

Tired of writing toString() on all your classes? Annoyed when your co-workers forget to do so? Here’s a simple solution that uses reflection to implement toString().

Find two source files. ToString.java is a helper class for implementing toString(). It’s a revision of the Google Guava’s ToStringHelper.

public class ToString {
	private final List<ValueHolder> valueHolders = new LinkedList<ValueHolder>();
	private boolean omitNullValues = false;

	protected final Object obj;

	public ToString(Object obj) {
		this.obj = obj;
	}

	public ToString add(String name, Object value) {
		addHolder(value).builder.append(name).append('=').append(value);
		return this;
	}

	public ToString add(String name, boolean value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, char value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, double value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, float value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, int value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, long value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	private StringBuilder checkNameAndAppend(String name) {
		return addHolder().builder.append(name).append('=');
	}

	@Override
	public String toString() {
		// create a copy to keep it consistent in case value changes
		boolean omitNullValuesSnapshot = omitNullValues;
		boolean needsSeparator = false;
		StringBuilder builder = new StringBuilder(32).append(
				obj.getClass().getSimpleName()).append('{');
		for (ValueHolder valueHolder : valueHolders) {
			if (!omitNullValuesSnapshot || !valueHolder.isNull) {
				if (needsSeparator) {
					builder.append(", ");
				} else {
					needsSeparator = true;
				}
				CharSequence sequence = valueHolder.builder;
				builder.append(sequence);
			}
		}
		return builder.append('}').toString();
	}

	private ValueHolder addHolder() {
		ValueHolder valueHolder = new ValueHolder();
		valueHolders.add(valueHolder);
		return valueHolder;
	}

	private ValueHolder addHolder(Object value) {
		ValueHolder valueHolder = addHolder();
		valueHolder.isNull = (value == null);
		return valueHolder;
	}

	private static final class ValueHolder {
		final StringBuilder builder = new StringBuilder();
		boolean isNull;
	}
}

ReflectiveToString.java extends the former, and re-implements toString() to automatically add all (most) fields.

public class ReflectiveToString extends ToString {
	private static final Pattern[] IGNORE_FIELD_PATTERNS = new Pattern[] {
			Pattern.compile("^this\\$\\d+$"),
			Pattern.compile("^serialVersionUID$") };

	public ReflectiveToString(Object obj) {
		super(obj);
	}

	@Override
	public String toString() {
		Set<Field> fields = getFields();
		fields: for (Field f : fields) {
			String fn = f.getName();
			for (Pattern p : IGNORE_FIELD_PATTERNS) {
				Matcher m = p.matcher(fn);
				if (m.matches()) {
					continue fields;
				}
			}

			f.setAccessible(true);
			try {
				add(fn, f.get(obj));
			} catch (IllegalArgumentException e) {
				// silently skip, should never happen
			} catch (IllegalAccessException e) {
				// silently skip, should never happen
			}
		}

		return super.toString();
	}

	private Set<Field> getFields() {
		Set<Field> fields = new HashSet<Field>();
		getFields(obj.getClass(), fields);

		return fields;
	}

	private static void getFields(Class<? extends Object> cls, Set<Field> fields) {
		Class<? extends Object> superclass = cls.getSuperclass();
		if (superclass != null) {
			getFields(superclass, fields);
		}
		fields.addAll(Arrays.asList(cls.getDeclaredFields()));
	}
}

To use it, implement toString() on your classes like this,

@Override
public String toString() {
    return new ReflectiveToString(this).toString();
}

You could of course extend Guava’s ToStringHelper directly. I didn’t want to pull in the entire library however.

Of course the string output can be tuned by modifying ToString.java.