Xtext. Can't add HyperlinkHelper - xtext

I try to customize HyperlinkHelper. So I have override HypertextDetector
package org.xtext.example.mydsl.ui;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.xtext.ui.editor.hyperlinking.DefaultHyperlinkDetector;
import org.eclipse.xtext.ui.editor.hyperlinking.IHyperlinkHelper;
public class MyHyperlinkDetector extends DefaultHyperlinkDetector {
private static final String PREFERENCES = ".hyper";
#Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
IDocument document = textViewer.getDocument();
int offset = region.getOffset();
// extract relevant characters
IRegion lineRegion;
String candidate;
try {
lineRegion = document.getLineInformationOfOffset(offset);
candidate = document.get(lineRegion.getOffset(), lineRegion.getLength());
} catch (BadLocationException ex) {
return null;
}
// look for keyword
int index = candidate.indexOf(PREFERENCES);
if (index != -1) {
// detect region containing keyword
IRegion targetRegion = new Region(lineRegion.getOffset() + index, PREFERENCES.length());
if ((targetRegion.getOffset() <= offset)
&& ((targetRegion.getOffset() + targetRegion.getLength()) > offset))
// create link
return new IHyperlink[] { new PreferencesHyperlink(targetRegion, candidate) };
}
return null;
}
#Override
public IHyperlinkHelper getHelper() {
// TODO Auto-generated method stub
return new MyHyperlinkHelper();
}
}
Hyperlink detector is worked, but MyHyperlinkHelper is never created. Even if I comment method detectHyperlinks.
My goal is to open file with name what I have click in my edited dsl grammar. That's why I need HyperlinkHelper. I.e. I need to check does my substring is correct file name.
How to solve it?
Regards,
Vladimir.

dont override the method. simply use guice and call the method from the superclass in your impl
public Class<? extends IHyperlinkHelper> bindIHyperlinkHelper() {
return DomainmodelHyperlinkHelper.class;
}
or in Xtend
def Class<? extends IHyperlinkHelper> bindIHyperlinkHelper() {
return DomainmodelHyperlinkHelper;
}

Related

how to get type of identifier dart-analyzer

I am processing method statements in a class and i want to find type of an identifier in those statements.
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:dstore/dstore.dart';
import 'package:dstore_generator/src/utils.dart';
import 'package:source_gen/source_gen.dart';
AstNode getAstNodeFromElement(Element element) {
AnalysisSession session = element.session;
ParsedLibraryResult parsedLibResult =
session.getParsedLibraryByElement(element.library);
ElementDeclarationResult elDeclarationResult =
parsedLibResult.getElementDeclaration(element);
return elDeclarationResult.node;
}
class SelectorsGenerator extends GeneratorForAnnotation<Selectors> {
#override
String generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) {
if (!(element is ClassElement)) {
throw Exception("Selectors should be applied on class only");
}
final className = element.name;
if (!className.startsWith("_")) {
throw Exception("Selectors functions class should start with _");
}
final modelName = className.substring(1);
final visitor = SelectorsVisitor(modelName);
final astNode = getAstNodeFromElement(element);
astNode.visitChildren(visitor);
return """
// Selector
""";
}
}
class SelectorsVisitor extends SimpleAstVisitor {
final String modelName;
final selectors = <String>[];
SelectorsVisitor(this.modelName);
#override
dynamic visitMethodDeclaration(MethodDeclaration node) {
final fields = convertParamsToFields(node.parameters);
if (fields.isEmpty || fields.length > 1) {
throw Exception(
"Selector functions should be only one param with app state");
}
final field = fields.first;
var name = node.name.toString();
if (node.returnType == null) {
throw Exception("You sould annontate return type of method ${name} ");
}
final rType = node.returnType.toString();
final sType = field.type;
final bvs = SelectorBodyVisitor(field.param!.identifier);
node.body.visitChildren(bvs);
// node.body.visitChildren(visitor)
return super.visitMethodDeclaration(node);
}
}
class SelectorBodyVisitor extends RecursiveAstVisitor {
final Identifier identifier;
final List<List<String>> depsList = [];
SelectorBodyVisitor(this.identifier);
List<String> getListOfPropAccess(PropertyAccess node) {
final result = <String>[];
final prop = node.propertyName.toString();
result.add(prop);
final target = node.target;
print("target type ${target.runtimeType}");
if (target is PropertyAccess) {
result.addAll(getListOfPropAccess(target));
} else if (target is PrefixedIdentifier) {
if (target.prefix.toString() == identifier.toString()) {
// I am trying to get identifier type here
print("IdentifierElement2 ${target.identifier.staticType}");
result.add(target.identifier.toString());
} else {
print("target is not identifier ${target.runtimeType} ${target}");
}
}
return result;
}
#override
dynamic visitPropertyAccess(PropertyAccess node) {
print("***&&& propsAccess ${node}");
final list = getListOfPropAccess(node);
final sa = node.toString().split(".").toList();
if (sa.length - 1 == list.length) {
// property access of state identifier
depsList.add(list.reversed.toList().take(2).toList());
}
print("Property access list ++++=== ++++++ ${list}");
}
#override
dynamic visitPrefixedIdentifier(PrefixedIdentifier node) {
print(
"**##### IdenAccess ${node} id: ${node.identifier} prefix : ${node.prefix} mid :${identifier.toString()}");
if (node.prefix.toString() == identifier.toString()) {
print("IdentifierElement1 ${node.identifier.staticType}");
depsList.add([node.identifier.toString()]);
} else {
print("identifier is not equal ${node.prefix == identifier}");
}
}
}
Example
class User {
final String name;
User(this.name)
}
class Model {
final User user;
Model(this.user)
}
#Selectors()
abstract class MySelectors {
static s1(Model model) {
final n = model.user.name; // i want to know type of name in code generation time , please check this line print("IdentifierElement2 ${target.identifier.staticType}"); in above code , where i am getting null
}
}
Found answer , I have to use getResolvedLibraryByElement instead of getParsedLibraryByElement in getAstNodeFromElement method.
Future<AstNode> getAstNodeFromElement(Element element) async {
AnalysisSession session = element.session;
final s = await session.getResolvedLibraryByElement(element.library);
final s2 = s.getElementDeclaration(element);
return s2.node;
}

Is this the best way to get all elements & attributes in an XML file using Saxon?

Our program displays a tree control showing the metadata structure of the XML file they are using as a datasource. So it displays all elements & attributes in use in the XML file, like this:
Employees
Employee
FirstName
LastName
Orders
Order
OrderId
For the case where the user does not pass us a XSD file, we need to walk the XML file and build up the metadata structure.
The full code for this is at SaxonQuestions.zip, TestBuildTree.java and is also listed below.
I am concerned that my code is not the most efficient, or maybe even wrong. It works, but it works on the 3 XML files I tested it on. My questions are:
What is the best way to get the root element from the DOM? Is it walking the children of the DOM root node?
What is the best way to determine if an element has data (as opposed to just child elements)? The best I could come up with throws an exception if not and I don't think code executing the happy path should throw an exception.
What is the best way to get the class of the data held in an element or attribute? Is it: ((XdmAtomicValue)((XdmNode)currentNode).getTypedValue()).getValue().getClass();
Is the best way to walk all the nodes to use XdmNode.axisIterator? And to do so as I have in this code?
TestBuildTree.java
import net.sf.saxon.s9api.*;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
public class TestBuildTree {
public static void main(String[] args) throws Exception {
XmlDatasource datasource = new XmlDatasource(
new FileInputStream(new File("files", "SouthWind.xml").getCanonicalPath()),
null);
// Question:
// Is this the best way to get the root element?
// get the root element
XdmNode rootNode = null;
for (XdmNode node : datasource.getXmlRootNode().children()) {
if (node.getNodeKind() == XdmNodeKind.ELEMENT) {
rootNode = node;
break;
}
}
TestBuildTree buildTree = new TestBuildTree(rootNode);
Element root = buildTree.addNode();
System.out.println("Schema:");
printElement("", root);
}
private static void printElement(String indent, Element element) {
System.out.println(indent + "<" + element.name + "> (" + (element.type == null ? "null" : element.type.getSimpleName()) + ")");
indent += " ";
for (Attribute attr : element.attributes)
System.out.println(indent + "=" + attr.name + " (" + (attr.type == null ? "null" : attr.type.getSimpleName()) + ")");
for (Element child : element.children)
printElement(indent, child);
}
protected XdmItem currentNode;
public TestBuildTree(XdmItem currentNode) {
this.currentNode = currentNode;
}
private Element addNode() throws SaxonApiException {
String name = ((XdmNode)currentNode).getNodeName().getLocalName();
// Question:
// Is this the best way to determine that this element has data (as opposed to child elements)?
Boolean elementHasData;
try {
((XdmNode) currentNode).getTypedValue();
elementHasData = true;
} catch (Exception ex) {
elementHasData = false;
}
// Questions:
// Is this the best way to get the type of the element value?
// If no schema is it always String?
Class valueClass = ! elementHasData ? null : ((XdmAtomicValue)((XdmNode)currentNode).getTypedValue()).getValue().getClass();
Element element = new Element(name, valueClass, null);
// add in attributes
XdmSequenceIterator currentSequence;
if ((currentSequence = moveTo(Axis.ATTRIBUTE)) != null) {
do {
name = ((XdmNode) currentNode).getNodeName().getLocalName();
// Questions:
// Is this the best way to get the type of the attribute value?
// If no schema is it always String?
valueClass = ((XdmAtomicValue)((XdmNode)currentNode).getTypedValue()).getValue().getClass();
Attribute attr = new Attribute(name, valueClass, null);
element.attributes.add(attr);
} while (moveToNextInCurrentSequence(currentSequence));
moveTo(Axis.PARENT);
}
// add in children elements
if ((currentSequence = moveTo(Axis.CHILD)) != null) {
do {
Element child = addNode();
// if we don't have this, add it
Element existing = element.getChildByName(child.name);
if (existing == null)
element.children.add(child);
else
// add in any children this does not have
existing.addNewItems (child);
} while (moveToNextInCurrentSequence(currentSequence));
moveTo(Axis.PARENT);
}
return element;
}
// moves to element or attribute
private XdmSequenceIterator moveTo(Axis axis) {
XdmSequenceIterator en = ((XdmNode) currentNode).axisIterator(axis);
boolean gotIt = false;
while (en.hasNext()) {
currentNode = en.next();
if (((XdmNode) currentNode).getNodeKind() == XdmNodeKind.ELEMENT || ((XdmNode) currentNode).getNodeKind() == XdmNodeKind.ATTRIBUTE) {
gotIt = true;
break;
}
}
if (gotIt) {
if (axis == Axis.ATTRIBUTE || axis == Axis.CHILD || axis == Axis.NAMESPACE)
return en;
return null;
}
return null;
}
// moves to next element or attribute
private Boolean moveToNextInCurrentSequence(XdmSequenceIterator currentSequence)
{
if (currentSequence == null)
return false;
while (currentSequence.hasNext())
{
currentNode = currentSequence.next();
if (((XdmNode)currentNode).getNodeKind() == XdmNodeKind.ELEMENT || ((XdmNode)currentNode).getNodeKind() == XdmNodeKind.ATTRIBUTE)
return true;
}
return false;
}
static class Node {
String name;
Class type;
String description;
public Node(String name, Class type, String description) {
this.name = name;
this.type = type;
this.description = description;
}
}
static class Element extends Node {
List<Element> children;
List<Attribute> attributes;
public Element(String name, Class type, String description) {
super(name, type, description);
children = new ArrayList<>();
attributes = new ArrayList<>();
}
public Element getChildByName(String name) {
for (Element child : children) {
if (child.name.equals(name))
return child;
}
return null;
}
public void addNewItems(Element child) {
for (Attribute attrAdd : child.attributes) {
boolean haveIt = false;
for (Attribute attrExist : attributes)
if (attrExist.name.equals(attrAdd.name)) {
haveIt = true;
break;
}
if (!haveIt)
attributes.add(attrAdd);
}
for (Element elemAdd : child.children) {
Element exist = null;
for (Element elemExist : children)
if (elemExist.name.equals(elemAdd.name)) {
exist = elemExist;
break;
}
if (exist == null)
children.add(elemAdd);
else
exist.addNewItems(elemAdd);
}
}
}
static class Attribute extends Node {
public Attribute(String name, Class type, String description) {
super(name, type, description);
}
}
}
XmlDatasource.java
import com.saxonica.config.EnterpriseConfiguration;
import com.saxonica.ee.s9api.SchemaValidatorImpl;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.FeatureKeys;
import net.sf.saxon.s9api.*;
import net.sf.saxon.type.SchemaException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
public class XmlDatasource {
/** the DOM all searches are against */
private XdmNode xmlRootNode;
private XPathCompiler xPathCompiler;
/** key == the prefix; value == the uri mapped to that prefix */
private HashMap<String, String> prefixToUriMap = new HashMap<>();
/** key == the uri mapped to that prefix; value == the prefix */
private HashMap<String, String> uriToPrefixMap = new HashMap<>();
public XmlDatasource (InputStream xmlData, InputStream schemaFile) throws SAXException, SchemaException, SaxonApiException, IOException {
boolean haveSchema = schemaFile != null;
// call this before any instantiation of Saxon classes.
Configuration config = createEnterpriseConfiguration();
if (haveSchema) {
Source schemaSource = new StreamSource(schemaFile);
config.addSchemaSource(schemaSource);
}
Processor processor = new Processor(config);
DocumentBuilder doc_builder = processor.newDocumentBuilder();
XMLReader reader = createXMLReader();
InputSource xmlSource = new InputSource(xmlData);
SAXSource saxSource = new SAXSource(reader, xmlSource);
if (haveSchema) {
SchemaValidator validator = new SchemaValidatorImpl(processor);
doc_builder.setSchemaValidator(validator);
}
xmlRootNode = doc_builder.build(saxSource);
xPathCompiler = processor.newXPathCompiler();
if (haveSchema)
xPathCompiler.setSchemaAware(true);
declareNameSpaces();
}
public XdmNode getXmlRootNode() {
return xmlRootNode;
}
public XPathCompiler getxPathCompiler() {
return xPathCompiler;
}
/**
* Create a XMLReader set to disallow XXE aattacks.
* #return a safe XMLReader.
*/
public static XMLReader createXMLReader() throws SAXException {
XMLReader reader = XMLReaderFactory.createXMLReader();
// stop XXE https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
return reader;
}
private void declareNameSpaces() throws SaxonApiException {
// saxon has some of their functions set up with this.
prefixToUriMap.put("saxon", "http://saxon.sf.net");
uriToPrefixMap.put("http://saxon.sf.net", "saxon");
XdmValue list = xPathCompiler.evaluate("//namespace::*", xmlRootNode);
if (list == null || list.size() == 0)
return;
for (int index=0; index<list.size(); index++) {
XdmNode node = (XdmNode) list.itemAt(index);
String prefix = node.getNodeName() == null ? "" : node.getNodeName().getLocalName();
// xml, xsd, & xsi are XML structure ones, not ones used in the XML
if (prefix.equals("xml") || prefix.equals("xsd") || prefix.equals("xsi"))
continue;
// use default prefix if prefix is empty.
if (prefix == null || prefix.isEmpty())
prefix = "def";
// this returns repeats, so if a repeat, go on to next.
if (prefixToUriMap.containsKey(prefix))
continue;
String uri = node.getStringValue();
if (uri != null && !uri.isEmpty()) {
xPathCompiler.declareNamespace(prefix, uri);
prefixToUriMap.put(prefix, uri);
uriToPrefixMap.put(uri, prefix); }
}
}
public static EnterpriseConfiguration createEnterpriseConfiguration()
{
EnterpriseConfiguration configuration = new EnterpriseConfiguration();
configuration.supplyLicenseKey(new BufferedReader(new java.io.StringReader(deobfuscate(key))));
configuration.setConfigurationProperty(FeatureKeys.SUPPRESS_XPATH_WARNINGS, Boolean.TRUE);
return configuration;
}
}

How to generate Dart code for annotations at fields?

I'm writing a code generator for Dart using the build_runner, but my builder is not being called for annotations at fields, although it does work for annotations at classes.
Is it possible to also call the generator for annotations at fields (or at any place for that matter)?
For example, the builder is called for the following file:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
int number;
}
But not for this one:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
class Fruit {
#MyAnnotation()
int number;
}
Here's the definition of the annotation:
class MyAnnotation {
const MyAnnotation();
}
And this is how the generator is defined. For now, it just aborts whenever it's called, causing an error message to be printed.
library my_annotation_generator;
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:my_annotation/my_annotation.dart';
import 'package:source_gen/source_gen.dart';
Builder generateAnnotation(BuilderOptions options) =>
SharedPartBuilder([MyAnnotationGenerator()], 'my_annotation');
class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
#override
generateForAnnotatedElement(Element element, ConstantReader annotation, _) {
throw CodeGenError('Generating code for annotation is not implemented yet.');
}
Here's the build.yaml configuration:
targets:
$default:
builders:
my_annotation_generator|my_annotation:
enabled: true
builders:
my_annotation:
target: ":my_annotation_generator"
import: "package:my_annotation/my_annotation.dart"
builder_factories: ["generateAnnotation"]
build_extensions: { ".dart": [".my_annotation.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
At least from my experience, your file 'example.dart' would need at least one annotation above the class definition to be parsed by GeneratorForAnnotation.
example.dart:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
#MyFieldAnnotation()
int number;
}
To access annotations above class fields or class methods you could use a visitor to "visit" each child element and extract the source code information.
For example, to get information about the class fields you could override the method visitFieldElement and then access any annotations using the getter: element.metadata.
builder.dart:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:my_annotation/my_annotation.dart';
class MyAnnotationGenerator extends
GeneratorForAnnotation<MyAnnotation> {
#override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,){
return _generateSource(element);
}
String _generateSource(Element element) {
var visitor = ModelVisitor();
element.visitChildren(visitor);
return '''
// ${visitor.className}
// ${visitor.fields}
// ${visitor.metaData}
''';
}
}
class ModelVisitor extends SimpleElementVisitor {
DartType className;
Map<String, DartType> fields = {};
Map<String, dynamic> metaData = {};
#override
visitConstructorElement(ConstructorElement element) {
className = element.type.returnType;
}
#override
visitFieldElement(FieldElement element) {
fields[element.name] = element.type;
metaData[element.name] = element.metadata;
}
}
Note: In this example, _generateSource returns a commented statement. Without comments you would need to return well-formed dart source code, otherwise, the builder will terminate with an error.
For more information see:
Source Generation and Writing Your Own Package (The Boring Flutter Development Show, Ep. 22) https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s
The built-in GeneratorForAnnotation uses the LibraryElement's annotatedWith(...) method, which only checks for top-level annotations.
To also detect annotations on fields, you'll need to write something custom.
Here's the Generator I wrote for my project:
abstract class GeneratorForAnnotatedField<AnnotationType> extends Generator {
/// Returns the annotation of type [AnnotationType] of the given [element],
/// or [null] if it doesn't have any.
DartObject getAnnotation(Element element) {
final annotations =
TypeChecker.fromRuntime(AnnotationType).annotationsOf(element);
if (annotations.isEmpty) {
return null;
}
if (annotations.length > 1) {
throw Exception(
"You tried to add multiple #$AnnotationType() annotations to the "
"same element (${element.name}), but that's not possible.");
}
return annotations.single;
}
#override
String generate(LibraryReader library, BuildStep buildStep) {
final values = <String>{};
for (final element in library.allElements) {
if (element is ClassElement && !element.isEnum) {
for (final field in element.fields) {
final annotation = getAnnotation(field);
if (annotation != null) {
values.add(generateForAnnotatedField(
field,
ConstantReader(annotation),
));
}
}
}
}
return values.join('\n\n');
}
String generateForAnnotatedField(
FieldElement field, ConstantReader annotation);
}
I had a very similar issue trying to target specific methods within my annotated classes. Inspired by your answers I slightly modified the class annotation model_visitor to check the method annotation before selecting elements.
class ClassAnnotationModelVisitor extends SimpleElementVisitor<dynamic> {
String className;
Map<String, String> methods = <String, String>{};
Map<String, String> parameters = <String, String>{};
#override
dynamic visitConstructorElement(ConstructorElement element) {
final elementReturnType = element.type.returnType.toString();
className = elementReturnType.replaceFirst('*', '');
}
#override
dynamic visitMethodElement(MethodElement element) {
if (methodHasAnnotation(MethodAnnotation, element)) {
final functionReturnType = element.type.returnType.toString();
methods[element.name] = functionReturnType.replaceFirst('*', '');
parameters[element.name] = element.parameters.map((e) => e.name).join(' ,');
}
}
bool methodHasAnnotation(Type annotationType, MethodElement element) {
final annotations = TypeChecker.fromRuntime(annotationType).annotationsOf(element);
return !annotations.isEmpty;
}
}
Then, I can use the basic GeneratorForAnnotation class and generate for class and methodsArray.

How to make the image translucent and change the bounds in JAVA using Eclipse Android API level 2?

I used the HelloItemizedOverlay code from Google to create and overlay item and it works but now I need to make the image translucent and change the bounds and I do not know how that works exactly.
I found the method getOpacity() but I do not know how to implement it in my ItemizedOverlay class or in my Map class. I am also aware of getIntrinsicHeight() and getIntrinsicWidth() methods but like the getOpacity() I do not know how to use it correctly. The code I have used for my ItemizedOverlay is :
import java.util.ArrayList;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.OverlayItem;
public class ContourItemizedOverlay extends ItemizedOverlay {
Context mContext;
private ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>();
public ContourItemizedOverlay(Drawable defaultMarker, Context context) {
super(boundCenter(defaultMarker));
mContext = context;
// TODO Auto-generated constructor stub
}
public int getOpacity()
{
return PixelFormat.TRANSPARENT;
}
#Override
protected boolean onTap(int index) {
OverlayItem item = mOverlays.get(index);
AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setTitle(item.getTitle());
dialog.setMessage(item.getSnippet());
dialog.show();
return true;
}
public void addOverlay(OverlayItem overlay) {
mOverlays.add(overlay);
populate();
}
#Override
protected OverlayItem createItem(int i) {
// TODO Auto-generated method stub
return mOverlays.get(i);
}
#Override
public int size() {
// TODO Auto-generated method stub
return mOverlays.size();
}
}

How to sort the choices in a Wicket dropdown according to the current user locale?

I have the following issue:
a drop down with a list of elements
each of these elements has a fixed key, which is used by the IChoiceRenderer implementation to look up the localized version of the key (it's a standard, utility renderer implemented in a different package)
the list of localized keys is in a properties file, linked to the panel which instantiates the dropdown.
Is there an elegant/reusable solution to have the dropdown display its elements sorted alphabetically ?
In the end, I think using the render is probably the best approach. To make it reusable and efficient, I isolated this in a Behavior.
Here's the code:
import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.form.AbstractChoice;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import static java.util.Arrays.sort;
/**
* This {#link Behavior} can only be used on {#link AbstractChoice} subclasses. It will sort the choices
* according to their "natural display order" (i.e. the natural order of the display values of the choices).
* This assumes that the display value implements {#link Comparable}. If this is not the case, you should
* provide a comparator for the display value. An instance of this class <em>cannot be shared</em> between components.
* Because the rendering can be costly, the sort-computation is done only once, by default,
* unless you set to <code>false</code> the <code>sortOnlyOnce</code> argument in the constructor.
*
* #author donckels (created on 2012-06-07)
*/
#SuppressWarnings({"unchecked"})
public class OrderedChoiceBehavior extends Behavior {
// ----- instance fields -----
private Comparator displayValueComparator;
private boolean sortOnlyOnce = true;
private boolean sorted;
// ----- constructors -----
public OrderedChoiceBehavior() {
}
public OrderedChoiceBehavior(boolean sortOnlyOnce) {
this.sortOnlyOnce = sortOnlyOnce;
}
public OrderedChoiceBehavior(boolean sortOnlyOnce, Comparator displayValueComparator) {
this.sortOnlyOnce = sortOnlyOnce;
this.displayValueComparator = displayValueComparator;
}
// ----- public methods -----
#Override
public void beforeRender(Component component) {
if (this.sorted && this.sortOnlyOnce) { return;}
AbstractChoice owner = (AbstractChoice) component;
IChoiceRenderer choiceRenderer = owner.getChoiceRenderer();
List choices = owner.getChoices();
// Temporary data structure: store the actual rendered value with its initial index
Object[][] displayValuesWithIndex = new Object[choices.size()][2];
for (int i = 0, valuesSize = choices.size(); i < valuesSize; i++) {
Object value = choices.get(i);
displayValuesWithIndex[i][0] = choiceRenderer.getDisplayValue(value);
displayValuesWithIndex[i][1] = i;
}
sort(displayValuesWithIndex, new DisplayValueWithIndexComparator());
List valuesCopy = new ArrayList(choices);
for (int i = 0, length = displayValuesWithIndex.length; i < length; i++) {
Object[] displayValueWithIndex = displayValuesWithIndex[i];
int originalIndex = (Integer) displayValueWithIndex[1];
choices.set(i, valuesCopy.get(originalIndex));
}
this.sorted = true;
}
public Comparator getDisplayValueComparator() {
return this.displayValueComparator;
}
// ----- inner classes -----
private class DisplayValueWithIndexComparator implements Comparator<Object[]> {
// ----- Comparator -----
public int compare(Object[] left, Object[] right) {
Object leftDisplayValue = left[0];
Object rightDisplayValue = right[0];
if (null == leftDisplayValue) { return -1;}
if (null == rightDisplayValue) { return 1;}
if (null == getDisplayValueComparator()) {
return ((Comparable) leftDisplayValue).compareTo(rightDisplayValue);
} else {
return getDisplayValueComparator().compare(leftDisplayValue, rightDisplayValue);
}
}
}
}
Use this extension of DropDownChoice using Java's Collator (basically locale sensitive sorting - take national characters and national sorting rules into account)
Code tested with Wicket 6 and Java 5+:
import java.text.Collator;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import org.apache.wicket.Session;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.model.IModel;
import com.google.common.collect.Ordering;
/**
* DropDownChoice which sort its choices (or in HTML's terminology select's options) according it's localized value
* and using current locale based Collator so it's sorted how it should be in particular language (ie. including national characters,
* using right order).
*
* #author Michal Bernhard michal#bernhard.cz 2013
*
* #param <T>
*/
public class OrderedDropDownChoice<T> extends DropDownChoice<T> {
public OrderedDropDownChoice(String id, IModel<? extends List<? extends T>> choices, IChoiceRenderer<? super T> renderer) {
super(id, choices, renderer);
}
public OrderedDropDownChoice(String id, IModel<? extends List<? extends T>> choices) {
super(id, choices);
}
public OrderedDropDownChoice(String id) {
super(id);
}
public OrderedDropDownChoice(
String id,
IModel<T> model,
IModel<? extends List<? extends T>> choices,
IChoiceRenderer<? super T> renderer) {
super(id, model, choices, renderer);
}
#Override
public List<? extends T> getChoices() {
List<? extends T> unsortedChoices = super.getChoices();
List<? extends T> sortedChoices = Ordering.from(displayValueAlphabeticComparator()).sortedCopy(unsortedChoices);
return sortedChoices;
}
private Collator localeBasedTertiaryCollator() {
Locale currentLocale = Session.get().getLocale();
Collator collator = Collator.getInstance(currentLocale);
collator.setStrength(Collator.TERTIARY);
return collator;
}
private Comparator<T> displayValueAlphabeticComparator() {
final IChoiceRenderer<? super T> renderer = getChoiceRenderer();
return new Comparator<T>() {
#Override
public int compare(T o1, T o2) {
Object o1DisplayValue = renderer.getDisplayValue(o1);
Object o2DisplayValue = renderer.getDisplayValue(o2);
return localeBasedTertiaryCollator().compare(o1DisplayValue, o2DisplayValue);
}
};
}
}
Copied from https://gist.github.com/michalbcz/7236242
If you want a Wicket-based solution you can try to sort the list with something like that:
public class ChoiceRendererComparator<T> implements Comparator<T> {
private final IChoiceRenderer<T> renderer;
public ChoiceRendererComparator(IChoiceRenderer<T> renderer) {
this.renderer = renderer;
}
#SuppressWarnings("unchecked")
public int compare(T o1, T o2) {
return ((Comparable<Object>) renderer.getDisplayValue(o1)).compareTo(renderer.getDisplayValue(o2));
}
}
Usage:
List<Entity> list = ...
IChoiceRenderer<Entity> renderer = ...
Collections.sort(list, new ChoiceRendererComparator<Entity>(renderer));
DropDownChoice<Entity> dropdown = new DropDownChoice<Entity>("dropdown", list, renderer);
The solution we use at my company is Javascript based, we set a special css class on the dropdowns we want to be sorted, and a little jQuery trick does the sort.
Facing the same problem, I moved part of the localisation data from my XMLs to the database, implemented a matching Resolver and was able to use the localized Strings for sorting.
The table design and hibernate configuration was kind of tricky and is described here: Hibernate #ElementCollection - Better solution needed.
The ResourceLoader is along these lines:
public class DataBaseStringResourceLoader extends ComponentStringResourceLoader {
private static final transient Logger logger = Logger
.getLogger(DataBaseStringResourceLoader.class);
#Inject
private ISomeDAO someDao;
#Inject
private IOtherDao otherDao;
#Inject
private IThisDAO thisDao;
#Inject
private IThatDAO thatDao;
#Override
public String loadStringResource(Class<?> clazz, String key, Locale locale,
String style, String variation) {
String resource = loadFromDB(key, new Locale(locale.getLanguage()));
if (resource == null) {
resource = super.loadStringResource(clazz, key, locale, style, variation);
}
return resource;
}
private String loadFromDB(String key, Locale locale) {
String resource = null;
if (locale.getLanguage() != Locale.GERMAN.getLanguage()
&& locale.getLanguage() != Locale.ENGLISH.getLanguage()) {
locale = Locale.ENGLISH;
}
if (key.startsWith("some") || key.startsWith("other")
|| key.startsWith("this") || key.startsWith("that")) {
Integer id = Integer.valueOf(key.substring(key.indexOf(".") + 1));
ILocalizedObject master;
if (key.startsWith("some")) {
master = someDao.findById(id);
} else if (key.startsWith("other")) {
master = otherDao.findById(id);
} else if (key.startsWith("this") ){
master = thisDao.findById(id);
} else {
master = thatDao.findById(id);
}
if (master != null && master.getNames().get(locale) != null) {
resource = master.getNames().get(locale).getName();
} else if (master == null) {
logger.debug("For key " + key + " there is no master.");
}
}
return resource;
}
[...]
}

Resources