com.force.i18n.LabelDebugProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grammaticus Show documentation
Show all versions of grammaticus Show documentation
Localization Framework that allows grammatically correct renaming of nouns
/*
* Copyright (c) 2017, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
package com.force.i18n;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import com.force.i18n.commons.text.TextUtil;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
/**
* Provide access to LabelDebugging. This class has two distinct implementations, one which does nothing
* and one which may add some helpful hints to the end of every label on a page and keep a list of those
* labels around for display.
*
* @author stamm (from rchen/150)
*/
public abstract class LabelDebugProvider {
// TODO SLT: Switch to an enum
protected static final String TRACE = "trace";
protected static final String MASK = "mask";
private volatile static LabelDebugProvider INSTANCE = new DisabledLabelDebugProvider(); // Default to disabled
public static LabelDebugProvider get() { return INSTANCE; }
/**
* Set the debug provider to be enabled or disabled
* @param enabled should the defaut debug provider be enabled
*/
public static void setLabelDebugProviderEnabled(boolean enabled) {
setLabelDebugProviderEnabled(enabled ? new EnabledLabelDebugProvider() : new DisabledLabelDebugProvider());
}
/**
* Set the debug provider to be used. Generally you want to use the boolean value and
* rely on the default behavior
* @param provider the labeldebugprovider to use globally
*/
public static void setLabelDebugProviderEnabled(LabelDebugProvider provider) {
Preconditions.checkNotNull(provider);
INSTANCE = provider;
}
/**
* Given a label reference, and it's text, possibly modify the hint to provide help
* @param text the text value of the label
* @param section the section for the label
* @param key the key for the label
* @return the text passed in, possibly with some hinting information appended.
*/
public abstract String makeLabelHintIfRequested(String text, String section, String key);
/**
* @return whether or not label debugging is ever allowed
*/
public abstract boolean isAllowed();
/**
* @return if the current Request should show the Label Debugging images.
*/
public boolean isLabelHintRequest() {
return false;
}
/**
* @return a continuation of the current label debug list so that it can survive
* re-establishing the localization context
*/
public LabelDebugContinuation getContinuation() {
return null;
}
/**
* @param continuation the continuation returned from getContinuation to use to establish the request afterwards
*/
public void setContinuation(LabelDebugContinuation continuation) {
}
/**
* @return whether we are tracking label usage or not
*/
public abstract boolean isTrackingLabelUsage();
/**
* Set whether we are tracking label usage or not
* @param trackUsage whether usage should be tracked
*
* Note: This is not threadsafe, and you should synchronize on the object before setting.
*/
public abstract void setTrackingLabelUsage(boolean trackUsage);
/**
* Mark the label as being tracked:
* @param section the section of the label
* @param key the key of the label
*/
public abstract void trackLabel(String section, String key);
/**
* @return the set of labels used so far in the application, returned
* as an unmodifiable multimap from section to key
*/
public abstract SetMultimap getUsedLabels();
/**
* Sets the value of the current request as to whether it
* @param value the value of the
* @return the previous value of the request.
*/
public abstract boolean setLabelHintRequest(boolean value);
/**
* Set the label hint mode.
* @param value either null, TRACE or MASK
*/
public abstract void setLabelHintMode(String value);
public List getLabelDebugs() {
return ImmutableList.of();
}
/**
* Marker interface for a debug continuation so that label hints can survive reestablishing requests
*/
public static interface LabelDebugContinuation {}
/**
* Implementation of LabelDebugProvider that does nothing
*/
static final class DisabledLabelDebugProvider extends LabelDebugProvider {
@Override
public boolean isAllowed() {
return false;
}
@Override
public String makeLabelHintIfRequested(String text, String section, String key) {
return text;
}
@Override
public boolean setLabelHintRequest(boolean value) {
throw new UnsupportedOperationException("You shouldn't call this");
}
@Override
public SetMultimap getUsedLabels() {
throw new UnsupportedOperationException("You shouldn't call this");
}
@Override
public boolean isTrackingLabelUsage() {
return false;
}
@Override
public void trackLabel(String section, String key) {
// Do nothing
}
@Override
public void setTrackingLabelUsage(boolean trackUsage) {
throw new UnsupportedOperationException("You shouldn't call this");
}
@Override
public void setLabelHintMode(String value) {
throw new UnsupportedOperationException("You shouldn't call this");
}
}
/**
* Implementation of LabelDebugProvider that stores the labels used in the current request.
*/
static final class EnabledLabelDebugProvider extends LabelDebugProvider {
private static final int STACK_LENGTH = 1300;
// Big f'n switch for whether we're doing label debugging or not. Gated by Debug.isDebug()
private static final ThreadLocal IS_DEBUGGING = new ThreadLocal() {
@Override protected Boolean initialValue() { return Boolean.FALSE; }
};
// If true, it means that we're tracking the total set of labels
private boolean isTracking = false;
// Yes unsynchronized, to prevent a big map we're going to do something a little fancier in the code itself
private final Set usedLabels = Collections.newSetFromMap(new ConcurrentHashMap(256, .75f, 16));
// Keeps track of labels from this Thread (i.e. request)
private static final ThreadLocal> LABEL_DEBUGS = new ThreadLocal>() {
@Override
protected List initialValue() {
return new ArrayList(1000); // It seems most pages have a few hundred labels
}
};
private String hintMode;
public EnabledLabelDebugProvider() {
super();
this.hintMode = TRACE;
}
@Override
public boolean isAllowed() {
return true;
}
/**
* @param text Text of the label, to be stored until printed in dev footer
* @param section Section
* @param key Key (aka param)
*/
@Override
public String makeLabelHintIfRequested(String text, String section, String key) {
if (text != null && isLabelHintRequest()) {
if (this.hintMode.equals(TRACE)) {
List labelDebugs = LABEL_DEBUGS.get();
// **3 is the MAGIC NUMBER**
// We start at 3 because we want to skip all the frames within Thread and Debug, if the call stack changes, this will have to as well.
String stackTrace = formatStackTrace(new Exception().getStackTrace(), 2, Integer.MAX_VALUE);
if (stackTrace.length() > STACK_LENGTH) {
stackTrace = stackTrace.substring(0, STACK_LENGTH);
}
labelDebugs.add(new LabelDebug(text, section, key, TextUtil.escapeToHtml(stackTrace)));
return text + "[#" + (labelDebugs.size() - 1) + "]";
} else if (this.hintMode.equals(MASK)) {
char[] mask = new char[text.length()];
Arrays.fill(mask, '#');
return new String(mask);
}
}
return text;
}
/**
* Determines if the current Request should show the Label Debugging images.
*/
@Override
public boolean isLabelHintRequest() {
return IS_DEBUGGING.get();
}
@Override
public boolean setLabelHintRequest(boolean value) {
boolean wasDebugging = IS_DEBUGGING.get();
IS_DEBUGGING.set(value);
if (wasDebugging && !value) {
LABEL_DEBUGS.remove(); // Clear out the thread if we were debugging and now are not (i.e. on release).
}
return wasDebugging;
}
@Override
public List getLabelDebugs() {
return LABEL_DEBUGS.get();
}
@Override
public void trackLabel(String section, String key) {
if (!isTracking) {
return;
}
usedLabels.add(new LabelRef(section, key));
}
@Override
public SetMultimap getUsedLabels() {
if (isTracking) {
// Make a nice sorted copy
TreeMultimap result = TreeMultimap.create();
for (LabelReference lr : usedLabels) {
result.put(lr.getSection(), lr.getKey());
}
return Multimaps.unmodifiableSetMultimap(result);
} else {
return TreeMultimap.create();
}
}
@Override
public boolean isTrackingLabelUsage() {
return isTracking;
}
@Override
public synchronized void setTrackingLabelUsage(boolean trackUsage) {
this.isTracking = trackUsage;
}
private static final LabelDebugContinuation NOT_TRACKING = new LabelDebugContinuation() {};
static final class RealLabelDebugContinuation implements LabelDebugContinuation {
private final List existingDebugs;
public RealLabelDebugContinuation(List existingDebugs) {
this.existingDebugs = existingDebugs;
}
public List getList() {
return this.existingDebugs;
}
}
@Override
public LabelDebugContinuation getContinuation() {
if (isLabelHintRequest()) {
return new RealLabelDebugContinuation(LABEL_DEBUGS.get());
} else {
return NOT_TRACKING;
}
}
@Override
public void setContinuation(LabelDebugContinuation continuation) {
if (continuation == NOT_TRACKING) {
setLabelHintRequest(false);
} else {
setLabelHintRequest(true);
assert continuation instanceof RealLabelDebugContinuation;
LABEL_DEBUGS.set(((RealLabelDebugContinuation)continuation).getList());
}
}
@Override
public void setLabelHintMode(String value) {
this.hintMode = value;
}
}
/*
* Format the stack trace in the range and stop when meet stop class name
* @param stack Stack trace elements
* @param startFrame The index of elements to start with
* @param maxFrame The max number of elements to format
* @param stopAtText The class names to stop at. Use : as separator for multiple stop classes, e.g., com.caucho:org.eclipse.jetty
* @return Stack trace in a formated string
*/
// TODO: Move this to I18nJavaUtils?
public static String formatStackTrace(StackTraceElement[] stack, int startFrame, int maxFrames) {
if (stack == null) {
return null;
}
StringBuilder result = new StringBuilder();
// cast to long to deal with overflow if startFrame + maxFrames > Integer.MAX_VALUE
int endAt = (int) Math.min(stack.length, (long) startFrame + (long) maxFrames);
for (int i = startFrame; i < endAt; i++) {
result.append('\n');
result.append(stack[i].toString());
}
return result.toString();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy