DescriptorsUtilspublic final class DescriptorsUtils extends Object Utility methods related to descriptors handling. |
Fields Summary |
---|
private static final String | DEFAULT_WIDGET_PREFIX | private static final int | JAVADOC_BREAK_LENGTH | public static final String | MANIFEST_SDK_URLThe path in the online documentation for the manifest description.
This is NOT a complete URL. To be used, it needs to be appended
to {@link AndroidConstants#CODESITE_BASE_URL} or to the local SDK
documentation. | public static final String | IMAGE_KEY | private static final String | CODE | private static final String | LINK | private static final String | ELEM | private static final String | BREAK |
Methods Summary |
---|
public static void | appendAttribute(java.util.ArrayList attributes, java.lang.String elementXmlName, java.lang.String nsUri, com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo info, boolean required, java.util.Map overrides)Add an {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
AttributeDescriptor attr = null;
String xmlLocalName = info.getName();
String uiName = prettyAttributeUiName(info.getName()); // ui_name
if (required) {
uiName += "*"; //$NON-NLS-1$
}
String tooltip = null;
String rawTooltip = info.getJavaDoc();
if (rawTooltip == null) {
rawTooltip = "";
}
String deprecated = info.getDeprecatedDoc();
if (deprecated != null) {
if (rawTooltip.length() > 0) {
rawTooltip += "@@"; //$NON-NLS-1$ insert a break
}
rawTooltip += "* Deprecated";
if (deprecated.length() != 0) {
rawTooltip += ": " + deprecated; //$NON-NLS-1$
}
if (deprecated.length() == 0 || !deprecated.endsWith(".")) { //$NON-NLS-1$
rawTooltip += "."; //$NON-NLS-1$
}
}
// Add the known types to the tooltip
Format[] formats_list = info.getFormats();
int flen = formats_list.length;
if (flen > 0) {
// Fill the formats in a set for faster access
HashSet<Format> formats_set = new HashSet<Format>();
StringBuilder sb = new StringBuilder();
if (rawTooltip != null && rawTooltip.length() > 0) {
sb.append(rawTooltip);
sb.append(" "); //$NON-NLS-1$
}
if (sb.length() > 0) {
sb.append("@@"); //$NON-NLS-1$ @@ inserts a break before the types
}
sb.append("["); //$NON-NLS-1$
for (int i = 0; i < flen; i++) {
Format f = formats_list[i];
formats_set.add(f);
sb.append(f.toString().toLowerCase());
if (i < flen - 1) {
sb.append(", "); //$NON-NLS-1$
}
}
// The extra space at the end makes the tooltip more readable on Windows.
sb.append("]"); //$NON-NLS-1$
if (required) {
sb.append(".@@* "); //$NON-NLS-1$ @@ inserts a break.
sb.append("Required.");
}
// The extra space at the end makes the tooltip more readable on Windows.
sb.append(" "); //$NON-NLS-1$
rawTooltip = sb.toString();
tooltip = formatTooltip(rawTooltip);
// Create a specialized attribute if we can
if (overrides != null) {
for (Entry<String, Object> entry: overrides.entrySet()) {
String key = entry.getKey();
String elements[] = key.split("/"); //$NON-NLS-1$
String overrideAttrLocalName = null;
if (elements.length < 1) {
continue;
} else if (elements.length == 1) {
overrideAttrLocalName = elements[0];
elements = null;
} else {
overrideAttrLocalName = elements[elements.length - 1];
elements = elements[0].split(","); //$NON-NLS-1$
}
if (overrideAttrLocalName == null ||
!overrideAttrLocalName.equals(xmlLocalName)) {
continue;
}
boolean ok_element = elements.length < 1;
if (!ok_element) {
for (String element : elements) {
if (element.equals("*") //$NON-NLS-1$
|| element.equals(elementXmlName)) {
ok_element = true;
break;
}
}
}
if (!ok_element) {
continue;
}
Object override = entry.getValue();
if (override instanceof Class) {
try {
// The override is instance of the class to create, which must
// have a constructor compatible with TextAttributeDescriptor.
@SuppressWarnings("unchecked") //$NON-NLS-1$
Class<? extends TextAttributeDescriptor> clazz =
(Class<? extends TextAttributeDescriptor>) override;
Constructor<? extends TextAttributeDescriptor> cons;
cons = clazz.getConstructor(new Class<?>[] {
String.class, String.class, String.class, String.class } );
attr = cons.newInstance(
new Object[] { xmlLocalName, uiName, nsUri, tooltip });
} catch (SecurityException e) {
// ignore
} catch (NoSuchMethodException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (InstantiationException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
}
} else if (override instanceof ITextAttributeCreator) {
attr = ((ITextAttributeCreator) override).create(
xmlLocalName, uiName, nsUri, tooltip);
}
}
} // if overrides
// Create a specialized descriptor if we can, based on type
if (attr == null) {
if (formats_set.contains(Format.REFERENCE)) {
// This is either a multi-type reference or a generic reference.
attr = new ReferenceAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
} else if (formats_set.contains(Format.ENUM)) {
attr = new ListAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip,
info.getEnumValues());
} else if (formats_set.contains(Format.FLAG)) {
attr = new FlagAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip,
info.getFlagValues());
} else if (formats_set.contains(Format.BOOLEAN)) {
attr = new BooleanAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
} else if (formats_set.contains(Format.STRING)) {
attr = new ReferenceAttributeDescriptor(ResourceType.STRING,
xmlLocalName, uiName, nsUri,
tooltip);
}
}
}
// By default a simple text field is used
if (attr == null) {
if (tooltip == null) {
tooltip = formatTooltip(rawTooltip);
}
attr = new TextAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
}
attr.setDeprecated(info.getDeprecatedDoc() != null);
attributes.add(attr);
| public static void | appendAttributes(java.util.ArrayList attributes, java.lang.String elementXmlName, java.lang.String nsUri, com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo[] infos, java.util.Set requiredAttributes, java.util.Map overrides)Add all {@link AttributeInfo} to the the array of {@link AttributeDescriptor}. //$NON-NLS-1$
for (AttributeInfo info : infos) {
boolean required = false;
if (requiredAttributes != null) {
String attr_name = info.getName();
if (requiredAttributes.contains("*/" + attr_name) ||
requiredAttributes.contains(elementXmlName + "/" + attr_name)) {
required = true;
}
}
appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides);
}
| public static java.lang.String | capitalize(java.lang.String str)Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
Returns the string unmodified if the first character is not [a-z].
if (str == null || str.length() < 1 || str.charAt(0) < 'a" || str.charAt(0) > 'z") {
return str;
}
StringBuilder sb = new StringBuilder();
sb.append((char)(str.charAt(0) + 'A" - 'a"));
sb.append(str.substring(1));
return sb.toString();
| private static java.lang.String | cleanupJavadocHtml(java.lang.String s)Remove anything that looks like HTML from a javadoc snippet, as it is supported
neither by FormText nor a standard text tooltip.
if (s != null) {
s = s.replaceAll("<", "\""); //$NON-NLS-1$ $NON-NLS-2$
s = s.replaceAll(">", "\""); //$NON-NLS-1$ $NON-NLS-2$
s = s.replaceAll("<[^>]+>", ""); //$NON-NLS-1$ $NON-NLS-2$
}
return s;
| public static boolean | containsAttribute(java.util.ArrayList attributes, java.lang.String nsUri, com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo info)Indicates the the given {@link AttributeInfo} already exists in the ArrayList of
{@link AttributeDescriptor}. This test for the presence of a descriptor with the same
XML name.
String xmlLocalName = info.getName();
for (AttributeDescriptor desc : attributes) {
if (desc.getXmlLocalName().equals(xmlLocalName)) {
if (nsUri == desc.getNamespaceUri() ||
(nsUri != null && nsUri.equals(desc.getNamespaceUri()))) {
return true;
}
}
}
return false;
| public static java.lang.String | formatFormText(java.lang.String javadoc, ElementDescriptor elementDescriptor, java.lang.String androidDocBaseUrl)Formats the javadoc tooltip to be usable in a FormText.
If the descriptor can provide an icon, the caller should provide
elementsDescriptor.getIcon() as "image" to FormText, e.g.:
formText.setImage(IMAGE_KEY, elementsDescriptor.getIcon());
ArrayList<String> spans = scanJavadoc(javadoc);
String fullSdkUrl = androidDocBaseUrl + MANIFEST_SDK_URL;
String sdkUrl = elementDescriptor.getSdkUrl();
if (sdkUrl != null && sdkUrl.startsWith(MANIFEST_SDK_URL)) {
fullSdkUrl = androidDocBaseUrl + sdkUrl;
}
StringBuilder sb = new StringBuilder();
Image icon = elementDescriptor.getIcon();
if (icon != null) {
sb.append("<form><li style=\"image\" value=\"" + //$NON-NLS-1$
IMAGE_KEY + "\">"); //$NON-NLS-1$
} else {
sb.append("<form><p>"); //$NON-NLS-1$
}
for (int n = spans.size(), i = 0; i < n; ++i) {
String s = spans.get(i);
if (CODE.equals(s)) {
s = spans.get(++i);
if (elementDescriptor.getXmlName().equals(s) && fullSdkUrl != null) {
sb.append("<a href=\""); //$NON-NLS-1$
sb.append(fullSdkUrl);
sb.append("\">"); //$NON-NLS-1$
sb.append(s);
sb.append("</a>"); //$NON-NLS-1$
} else if (s != null) {
sb.append('"").append(s).append('"");
}
} else if (LINK.equals(s)) {
String base = spans.get(++i);
String anchor = spans.get(++i);
String text = spans.get(++i);
if (base != null) {
base = base.trim();
}
if (anchor != null) {
anchor = anchor.trim();
}
if (text != null) {
text = text.trim();
}
// If there's no text, use the anchor if there's one
if (text == null || text.length() == 0) {
text = anchor;
}
// TODO specialize with a base URL for views, menus & other resources
// Base is empty for a local page anchor, in which case we'll replace it
// by the element SDK URL if it exists.
if ((base == null || base.length() == 0) && fullSdkUrl != null) {
base = fullSdkUrl;
}
String url = null;
if (base != null && base.length() > 0) {
if (base.startsWith("http")) { //$NON-NLS-1$
// If base looks an URL, use it, with the optional anchor
url = base;
if (anchor != null && anchor.length() > 0) {
// If the base URL already has an anchor, it needs to be
// removed first. If there's no anchor, we need to add "#"
int pos = url.lastIndexOf('#");
if (pos < 0) {
url += "#"; //$NON-NLS-1$
} else if (pos < url.length() - 1) {
url = url.substring(0, pos + 1);
}
url += anchor;
}
} else if (text == null || text.length() == 0) {
// If we still have no text, use the base as text
text = base;
}
}
if (url != null && text != null) {
sb.append("<a href=\""); //$NON-NLS-1$
sb.append(url);
sb.append("\">"); //$NON-NLS-1$
sb.append(text);
sb.append("</a>"); //$NON-NLS-1$
} else if (text != null) {
sb.append("<b>").append(text).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (ELEM.equals(s)) {
s = spans.get(++i);
if (sdkUrl != null && s != null) {
sb.append("<a href=\""); //$NON-NLS-1$
sb.append(sdkUrl);
sb.append("\">"); //$NON-NLS-1$
sb.append(s);
sb.append("</a>"); //$NON-NLS-1$
} else if (s != null) {
sb.append("<b>").append(s).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (BREAK.equals(s)) {
// ignore line breaks in pseudo-HTML rendering
} else if (s != null) {
sb.append(s);
}
}
if (icon != null) {
sb.append("</li></form>"); //$NON-NLS-1$
} else {
sb.append("</p></form>"); //$NON-NLS-1$
}
return sb.toString();
| public static java.lang.String | formatTooltip(java.lang.String javadoc)Formats the javadoc tooltip to be usable in a tooltip.
ArrayList<String> spans = scanJavadoc(javadoc);
StringBuilder sb = new StringBuilder();
boolean needBreak = false;
for (int n = spans.size(), i = 0; i < n; ++i) {
String s = spans.get(i);
if (CODE.equals(s)) {
s = spans.get(++i);
if (s != null) {
sb.append('"").append(s).append('"");
}
} else if (LINK.equals(s)) {
String base = spans.get(++i);
String anchor = spans.get(++i);
String text = spans.get(++i);
if (base != null) {
base = base.trim();
}
if (anchor != null) {
anchor = anchor.trim();
}
if (text != null) {
text = text.trim();
}
// If there's no text, use the anchor if there's one
if (text == null || text.length() == 0) {
text = anchor;
}
if (base != null && base.length() > 0) {
if (text == null || text.length() == 0) {
// If we still have no text, use the base as text
text = base;
}
}
if (text != null) {
sb.append(text);
}
} else if (ELEM.equals(s)) {
s = spans.get(++i);
if (s != null) {
sb.append(s);
}
} else if (BREAK.equals(s)) {
needBreak = true;
} else if (s != null) {
if (needBreak && s.trim().length() > 0) {
sb.append('\r");
}
sb.append(s);
needBreak = false;
}
}
return sb.toString();
| public static java.lang.String | getFreeWidgetId(com.android.ide.eclipse.editors.uimodel.UiElementNode uiNode)Given a UI root node, returns the first available id that matches the
pattern "prefix%02d".
return getFreeWidgetId(uiNode.getUiRoot(),
new Object[] { uiNode.getDescriptor().getXmlLocalName(), null, null, null });
| private static java.lang.String | getFreeWidgetId(com.android.ide.eclipse.editors.uimodel.UiElementNode uiRoot, java.lang.Object[] params)Given a UI root node, returns the first available id that matches the
pattern "prefix%02d".
For recursion purposes, a "context" is given. Since Java doesn't have in-out parameters
in methods and we're not going to do a dedicated type, we just use an object array which
must contain one initial item and several are built on the fly just for internal storage:
- prefix(String): The prefix of the generated id, i.e. "widget". Cannot be null.
- index(Integer): The minimum index of the generated id. Must start with null.
- generated(String): The generated widget currently being searched. Must start with null.
- map(Set): A set of the ids collected so far when walking through the widget
hierarchy. Must start with null.
Set<String> map = (Set<String>)params[3];
if (map == null) {
params[3] = map = new HashSet<String>();
}
int num = params[1] == null ? 0 : ((Integer)params[1]).intValue();
String generated = (String) params[2];
String prefix = (String) params[0];
if (generated == null) {
int pos = prefix.indexOf('.");
if (pos >= 0) {
prefix = prefix.substring(pos + 1);
}
pos = prefix.indexOf('$");
if (pos >= 0) {
prefix = prefix.substring(pos + 1);
}
prefix = prefix.replaceAll("[^a-zA-Z]", ""); //$NON-NLS-1$ $NON-NLS-2$
if (prefix.length() == 0) {
prefix = DEFAULT_WIDGET_PREFIX;
}
do {
num++;
generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$
} while (map.contains(generated));
params[0] = prefix;
params[1] = num;
params[2] = generated;
}
String id = uiRoot.getAttributeValue(LayoutConstants.ATTR_ID);
if (id != null) {
id = id.replace("@+id/", ""); //$NON-NLS-1$ $NON-NLS-2$
id = id.replace("@id/", ""); //$NON-NLS-1$ $NON-NLS-2$
if (map.add(id) && map.contains(generated)) {
do {
num++;
generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$
} while (map.contains(generated));
params[1] = num;
params[2] = generated;
}
}
for (UiElementNode uiChild : uiRoot.getUiChildren()) {
getFreeWidgetId(uiChild, params);
}
// Note: return params[2] (not "generated") since it could have changed during recursion.
return (String) params[2];
| public static java.lang.String | prettyAttributeUiName(java.lang.String name)Create a pretty attribute UI name from an XML name.
The original xml name starts with a lower case and is camel-case,
e.g. "maxWidthForView". The pretty name starts with an upper case
and has space separators, e.g. "Max width for view".
if (name.length() < 1) {
return name;
}
StringBuffer buf = new StringBuffer();
char c = name.charAt(0);
// Use upper case initial letter
buf.append((char)(c >= 'a" && c <= 'z" ? c + 'A" - 'a" : c));
int len = name.length();
for (int i = 1; i < len; i++) {
c = name.charAt(i);
if (c >= 'A" && c <= 'Z") {
// Break camel case into separate words
buf.append(' ");
// Use a lower case initial letter for the next word, except if the
// word is solely X, Y or Z.
if (c >= 'X" && c <= 'Z" &&
(i == len-1 ||
(i < len-1 && name.charAt(i+1) >= 'A" && name.charAt(i+1) <= 'Z"))) {
buf.append(c);
} else {
buf.append((char)(c - 'A" + 'a"));
}
} else if (c == '_") {
buf.append(' ");
} else {
buf.append(c);
}
}
name = buf.toString();
// Replace these acronyms by upper-case versions
// - (?<=^| ) means "if preceded by a space or beginning of string"
// - (?=$| ) means "if followed by a space or end of string"
name = name.replaceAll("(?<=^| )sdk(?=$| )", "SDK");
name = name.replaceAll("(?<=^| )uri(?=$| )", "URI");
return name;
| private static java.util.ArrayList | scanJavadoc(java.lang.String javadoc)
ArrayList<String> spans = new ArrayList<String>();
// Standardize all whitespace in the javadoc to single spaces.
if (javadoc != null) {
javadoc = javadoc.replaceAll("[ \t\f\r\n]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
}
// Detects {@link <base>#<name> <text>} where all 3 are optional
Pattern p_link = Pattern.compile("\\{@link\\s+([^#\\}\\s]*)(?:#([^\\s\\}]*))?(?:\\s*([^\\}]*))?\\}(.*)"); //$NON-NLS-1$
// Detects <code>blah</code>
Pattern p_code = Pattern.compile("<code>(.+?)</code>(.*)"); //$NON-NLS-1$
// Detects @blah@, used in hard-coded tooltip descriptors
Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$
// Detects a buffer that starts by @@ (request for a break)
Pattern p_break = Pattern.compile("@@(.*)"); //$NON-NLS-1$
// Detects a buffer that starts by @ < or { (one that was not matched above)
Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$
// Detects everything till the next potential separator, i.e. @ < or {
Pattern p_text = Pattern.compile("([^@<\\{]+)(.*)"); //$NON-NLS-1$
int currentLength = 0;
String text = null;
while(javadoc != null && javadoc.length() > 0) {
Matcher m;
String s = null;
if ((m = p_code.matcher(javadoc)).matches()) {
spans.add(CODE);
spans.add(text = cleanupJavadocHtml(m.group(1))); // <code> text
javadoc = m.group(2);
if (text != null) {
currentLength += text.length();
}
} else if ((m = p_link.matcher(javadoc)).matches()) {
spans.add(LINK);
spans.add(m.group(1)); // @link base
spans.add(m.group(2)); // @link anchor
spans.add(text = cleanupJavadocHtml(m.group(3))); // @link text
javadoc = m.group(4);
if (text != null) {
currentLength += text.length();
}
} else if ((m = p_elem.matcher(javadoc)).matches()) {
spans.add(ELEM);
spans.add(text = cleanupJavadocHtml(m.group(1))); // @text@
javadoc = m.group(2);
if (text != null) {
currentLength += text.length() - 2;
}
} else if ((m = p_break.matcher(javadoc)).matches()) {
spans.add(BREAK);
currentLength = 0;
javadoc = m.group(1);
} else if ((m = p_open.matcher(javadoc)).matches()) {
s = m.group(1);
javadoc = m.group(2);
} else if ((m = p_text.matcher(javadoc)).matches()) {
s = m.group(1);
javadoc = m.group(2);
} else {
// This is not supposed to happen. In case of, just use everything.
s = javadoc;
javadoc = null;
}
if (s != null && s.length() > 0) {
s = cleanupJavadocHtml(s);
if (currentLength >= JAVADOC_BREAK_LENGTH) {
spans.add(BREAK);
currentLength = 0;
}
while (currentLength + s.length() > JAVADOC_BREAK_LENGTH) {
int pos = s.indexOf(' ", JAVADOC_BREAK_LENGTH - currentLength);
if (pos <= 0) {
break;
}
spans.add(s.substring(0, pos + 1));
spans.add(BREAK);
currentLength = 0;
s = s.substring(pos + 1);
}
spans.add(s);
currentLength += s.length();
}
}
return spans;
| public static void | setDefaultLayoutAttributes(com.android.ide.eclipse.editors.uimodel.UiElementNode ui_node, boolean updateLayout)Sets the default layout attributes for the a new UiElementNode.
Note that ideally the node should already be part of a hierarchy so that its
parent layout and previous sibling can be determined, if any.
This does not override attributes which are not empty.
// if this ui_node is a layout and we're adding it to a document, use fill_parent for
// both W/H. Otherwise default to wrap_layout.
boolean fill = ui_node.getDescriptor().hasChildren() &&
ui_node.getUiParent() instanceof UiDocumentNode;
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_WIDTH,
fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT,
false /* override */);
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_HEIGHT,
fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT,
false /* override */);
String widget_id = getFreeWidgetId(ui_node.getUiRoot(),
new Object[] { ui_node.getDescriptor().getXmlLocalName(), null, null, null });
if (widget_id != null) {
ui_node.setAttributeValue(LayoutConstants.ATTR_ID, "@+id/" + widget_id, //$NON-NLS-1$
false /* override */);
}
ui_node.setAttributeValue(LayoutConstants.ATTR_TEXT, widget_id, false /*override*/);
if (updateLayout) {
UiElementNode ui_parent = ui_node.getUiParent();
if (ui_parent != null &&
ui_parent.getDescriptor().getXmlLocalName().equals(
LayoutConstants.RELATIVE_LAYOUT)) {
UiElementNode ui_previous = ui_node.getUiPreviousSibling();
if (ui_previous != null) {
String id = ui_previous.getAttributeValue(LayoutConstants.ATTR_ID);
if (id != null && id.length() > 0) {
id = id.replace("@+", "@"); //$NON-NLS-1$ //$NON-NLS-2$
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW, id,
false /* override */);
}
}
}
}
|
|