package com.github.czyzby.lml.parser.impl.tag.macro;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.utils.Array;
import com.github.czyzby.kiwi.util.common.Strings;
import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays;
import com.github.czyzby.kiwi.util.gdx.collection.GdxMaps;
import com.github.czyzby.lml.parser.LmlParser;
import com.github.czyzby.lml.parser.action.ActorConsumer;
import com.github.czyzby.lml.parser.impl.tag.AbstractMacroLmlTag;
import com.github.czyzby.lml.parser.tag.LmlAttribute;
import com.github.czyzby.lml.parser.tag.LmlTag;
/** You can register attributes using LML templates. Strictly speaking, there is hardly any advantage over
* implementing custom {@link LmlAttribute} and adding it to the LML syntax object, except doing it in LML does require
* less code and set-up. Macro requires exactly two attributes: an array of attribute aliases and a method that consumes
* {@link AttributeParsingData}. AttributeParsingData basically contains the everything that
* {@link LmlAttribute#process(LmlParser, LmlTag, Object, String)} method gets as its arguments. Let's say we want to
* define an argument that sets upper-cased text on a label:
*
* <blockquote>
*
* <pre>
* public void setUpperCaseText(AttributeParsingData data) {
* if (data.getActor() instanceof Label) {
* Label label = (Label) data.getActor();
* String text = data.getParser().parseString(data.getRawAttributeData(), label).toUpperCase();
* label.setText(text);
* }
* }
* </pre>
*
* </blockquote> Now you can register it in the templates an enjoy the new attribute: <blockquote>
*
* <pre>
* <:newAttribute upper;upperCase setUpperCaseText/>
* <label upper="value" />
* <label upperCase=@bundleLine>
* </pre>
*
* </blockquote>The first label will have "VALUE" as its text; the second will find "bundleLine" in the default i18n
* bundle, convert it to upper case and set the result as its text.
*
* <p>
* For the curious: this is a class with the same functionality (except now it actually applies only to labels, so we do
* not have to worry about classes, check type or do any casting). You would have to manually pass an instance of this
* class to LmlSyntax object to register it, though, so that's an extra Java code line.<blockquote>
*
* <pre>
* public class UpperCaseLmlAttribute implements LmlAttribute<Label> {
* @Override
* public Class<Label> getHandledType() {
* return Label.class;
* }
*
* @Override
* public void process(LmlParser parser, LmlTag tag, Label actor, String rawAttributeData) {
* actor.setText(parser.parseString(rawAttributeData, actor).toUpperCase());
* }
* }
* </pre>
*
* </blockquote>
*
* <p>
* Note that this macro supports named attributes:<blockquote>
*
* <pre>
* <:newAttribute alias="upper;upperCase" method="setUpperCaseText" />
* </pre>
*
* </blockquote>
*
* @author MJ */
public class NewAttributeLmlMacroTag extends AbstractMacroLmlTag {
/** Alias for the first macro attribute: list of attribute aliases. */
public static final String ALIAS_ATTRIBUTE = "alias";
/** Alias for the second macro attribute: name of the method that consumes {@link AttributeParsingData} instance. */
public static final String METHOD_ATTRIBUTE = "method";
public NewAttributeLmlMacroTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) {
super(parser, parentTag, rawTagData);
}
@Override
public void handleDataBetweenTags(final CharSequence rawData) {
if (Strings.isNotWhitespace(rawData)) {
getParser().throwErrorIfStrict("New attribute macro cannot parse content between tags.");
}
}
@Override
public void closeTag() {
final Array<String> attributes = getAttributes();
if (GdxArrays.sizeOf(attributes) < 2) {
getParser().throwError(
"Unable to create a new atrribute with less than two macro attributes: attribute aliases array and setter method consuming AttributeParsingData.");
}
// Possible tag names:
final String[] tagNames = getAttributeNames();
// Processes attribute parsing:
final ActorConsumer<?, AttributeParsingData> parser = getAttributeParser();
if (parser == null) {
getParser().throwError(
"Unable to add new attribute. Action consuming AttributeParsingData not found for name: "
+ attributes.get(1));
}
getParser().getSyntax().addAttributeProcessor(new CustomLmlAttribute(parser), tagNames);
}
/** @return {@link ActorConsumer} consuming {@link AttributeParsingData}. */
protected ActorConsumer<?, AttributeParsingData> getAttributeParser() {
if (hasAttribute(METHOD_ATTRIBUTE)) {
return getParser().parseAction(getAttribute(METHOD_ATTRIBUTE), new AttributeParsingData());
}
return getParser().parseAction(getAttributes().get(1), new AttributeParsingData());
}
/** @return list of attribute aliases. */
protected String[] getAttributeNames() {
if (hasAttribute(ALIAS_ATTRIBUTE)) {
return getParser().parseArray(getAttribute(ALIAS_ATTRIBUTE), getActor());
} else if (GdxMaps.isNotEmpty(getNamedAttributes())) {
getParser().throwError(
"When using named attributes, new attribute macro needs at least two attributes: 'method' (name of the method that consumes AttributeParsingData) and 'attribute' (array of new attribute aliases). Found attributes: "
+ getNamedAttributes());
}
return getParser().parseArray(getAttributes().first(), getActor());
}
@Override
public String[] getExpectedAttributes() {
return new String[] { ALIAS_ATTRIBUTE, METHOD_ATTRIBUTE };
}
/** Allows to register new attributes from within LML templates using new attribute macro.
*
* @author MJ */
public static class CustomLmlAttribute implements LmlAttribute<Actor> {
private final ActorConsumer<?, AttributeParsingData> attributeParser;
/** @param attributeParser wraps around a method that does the actual attribute parsing. */
public CustomLmlAttribute(final ActorConsumer<?, AttributeParsingData> attributeParser) {
this.attributeParser = attributeParser;
}
@Override
public Class<Actor> getHandledType() {
return Actor.class;
}
@Override
public void process(final LmlParser parser, final LmlTag tag, final Actor actor,
final String rawAttributeData) {
attributeParser.consume(new AttributeParsingData(parser, tag, actor, rawAttributeData));
}
}
/** Contains attribute parsing data normally passed as arguments to
* {@link LmlAttribute#process(LmlParser, LmlTag, Object, String)} method.
*
* @author MJ */
public static class AttributeParsingData {
private final LmlParser parser;
private final LmlTag tag;
private final Actor actor;
private final String rawAttributeData;
/** Utility private constructor. */
private AttributeParsingData() {
this(null, null, null, null);
}
/** @param parser handles LML template parsing.
* @param tag contains raw tag data. Allows to access actor's parent.
* @param actor handled actor instance.
* @param rawAttributeData unparsed LML attribute data that should be handled by an attribute processor. Common
* data types (string, int, float, boolean, action) are already handled by LML parser implementation,
* so make sure to invoke its methods. */
public AttributeParsingData(final LmlParser parser, final LmlTag tag, final Actor actor,
final String rawAttributeData) {
this.parser = parser;
this.tag = tag;
this.actor = actor;
this.rawAttributeData = rawAttributeData;
}
/** @return handles LML template parsing. */
public LmlParser getParser() {
return parser;
}
/** @return contains raw tag data. Allows to access actor's parent. */
public LmlTag getTag() {
return tag;
}
/** @return handled actor instance. Should have the attribute property set. */
public Actor getActor() {
return actor;
}
/** @return unparsed LML attribute data that should be handled by an attribute processor. Common data types
* (string, int, float, boolean, action) are already handled by LML parser implementation, so make sure
* to invoke its methods. */
public String getRawAttributeData() {
return rawAttributeData;
}
}
}