/*
* Copyright 2016 Flipkart Internet Pvt. Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flipkart.android.proteus.builder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import com.flipkart.android.proteus.DataContext;
import com.flipkart.android.proteus.binding.Binding;
import com.flipkart.android.proteus.parser.LayoutHandler;
import com.flipkart.android.proteus.toolbox.Formatter;
import com.flipkart.android.proteus.toolbox.IdGenerator;
import com.flipkart.android.proteus.toolbox.ProteusConstants;
import com.flipkart.android.proteus.toolbox.Result;
import com.flipkart.android.proteus.toolbox.Styles;
import com.flipkart.android.proteus.toolbox.Utils;
import com.flipkart.android.proteus.view.ProteusView;
import com.flipkart.android.proteus.view.manager.ProteusViewManager;
import com.flipkart.android.proteus.view.manager.ProteusViewManagerImpl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
/**
* A layout builder which can parse data bindings before passing it on to {@link SimpleLayoutBuilder}
*/
public class DataParsingLayoutBuilder extends SimpleLayoutBuilder {
private static final String TAG = "LayoutBuilder";
private Map<String, Formatter> formatter = new HashMap<>();
protected DataParsingLayoutBuilder(@NonNull IdGenerator idGenerator) {
super(idGenerator);
}
@Override
protected ProteusViewManager createViewManager(LayoutHandler handler, View parent, JsonObject layout, JsonObject data, int index, Styles styles) {
ProteusViewManagerImpl viewManager = new ProteusViewManagerImpl();
DataContext dataContext, parentDataContext = null;
JsonElement scope = layout.get(ProteusConstants.DATA_CONTEXT);
if (parent instanceof ProteusView) {
parentDataContext = ((ProteusView) parent).getViewManager().getDataContext();
}
if (scope == null || scope.isJsonNull()) {
if (parentDataContext != null) {
dataContext = new DataContext(parentDataContext);
} else {
dataContext = new DataContext();
dataContext.setData(data);
dataContext.setIndex(index);
}
} else {
if (parentDataContext != null) {
dataContext = parentDataContext.createChildDataContext(scope.getAsJsonObject(), index);
} else {
dataContext = new DataContext();
dataContext.setData(data);
dataContext = dataContext.createChildDataContext(scope.getAsJsonObject(), index);
}
}
viewManager.setLayout(layout);
viewManager.setDataContext(dataContext);
viewManager.setStyles(styles);
viewManager.setLayoutBuilder(this);
viewManager.setLayoutHandler(handler);
return viewManager;
}
@Override
public boolean handleAttribute(LayoutHandler handler, ProteusView view, String attribute, JsonElement value) {
if (ProteusConstants.DATA_CONTEXT.equals(attribute)) {
return true;
}
String stringValue = isDataPath(value);
if (stringValue != null) {
value = this.findAndReplaceValues(handler, view, attribute, stringValue, value);
}
return super.handleAttribute(handler, view, attribute, value);
}
private JsonElement findAndReplaceValues(LayoutHandler handler, ProteusView view, String attribute, String stringValue, JsonElement value) {
ProteusViewManager viewManager = view.getViewManager();
DataContext dataContext = viewManager.getDataContext();
if (ProteusConstants.isLoggingEnabled()) {
Log.d(TAG, "Find '" + stringValue + "' for " + attribute + " for view with " + Utils.getLayoutIdentifier(viewManager.getLayout()));
}
char firstChar = TextUtils.isEmpty(stringValue) ? 0 : stringValue.charAt(0);
String dataPath;
Result result;
switch (firstChar) {
case ProteusConstants.DATA_PREFIX:
JsonElement elementFromData;
dataPath = stringValue.substring(1);
result = Utils.readJson(dataPath, viewManager.getDataContext().getData(), viewManager.getDataContext().getIndex());
if (result.isSuccess()) {
elementFromData = result.element;
} else {
elementFromData = new JsonPrimitive(ProteusConstants.DATA_NULL);
}
if (elementFromData != null) {
value = elementFromData;
}
addBinding(viewManager, dataPath, attribute, stringValue, false);
break;
case ProteusConstants.REGEX_PREFIX:
Matcher regexMatcher = ProteusConstants.REGEX_PATTERN.matcher(stringValue);
String finalValue = stringValue;
while (regexMatcher.find()) {
String matchedString = regexMatcher.group(0);
String bindingName;
if (regexMatcher.group(3) != null) {
// has NO formatter
dataPath = regexMatcher.group(3);
result = Utils.readJson(dataPath, viewManager.getDataContext().getData(), viewManager.getDataContext().getIndex());
if (result.isSuccess() && null != result.element) {
finalValue = finalValue.replace(matchedString, result.element.getAsString());
} else {
finalValue = dataPath;
}
bindingName = dataPath;
} else {
// has formatter
dataPath = regexMatcher.group(1);
String formatterName = regexMatcher.group(2);
String formattedValue;
result = Utils.readJson(dataPath, viewManager.getDataContext().getData(), viewManager.getDataContext().getIndex());
if (result.isSuccess() && null != result.element) {
formattedValue = format(result.element, formatterName);
} else {
formattedValue = dataPath;
}
finalValue = finalValue.replace(matchedString, formattedValue != null ? formattedValue : "");
bindingName = dataPath;
}
addBinding(viewManager, bindingName, attribute, stringValue, true);
}
// remove the REGEX_PREFIX
finalValue = finalValue.substring(1);
// return as a JsonPrimitive
value = new JsonPrimitive(finalValue);
break;
}
return value;
}
public String isDataPath(JsonElement element) {
if (!element.isJsonPrimitive()) {
return null;
}
String attributeValue = element.getAsString();
if (attributeValue != null && !"".equals(attributeValue) &&
(attributeValue.charAt(0) == ProteusConstants.DATA_PREFIX || attributeValue.charAt(0) == ProteusConstants.REGEX_PREFIX)) {
return attributeValue;
}
return null;
}
private void addBinding(ProteusViewManager viewManager, String bindingName, String attributeName, String attributeValue, boolean hasRegEx) {
// check if the view is in update mode if not that means that the update flow
// is running and we must not add more bindings for they will be duplicates
if (!viewManager.isViewUpdating()) {
Binding binding = new Binding(bindingName, attributeName, attributeValue, hasRegEx);
viewManager.addBinding(binding);
}
}
private String format(JsonElement toFormat, String formatterName) {
Formatter formatter = this.formatter.get(formatterName);
if (formatter == null) {
formatter = Formatter.NOOP;
}
return formatter.format(toFormat);
}
@Nullable
protected JsonObject onLayoutRequired(String type, ProteusView parent) {
if (ProteusConstants.isLoggingEnabled()) {
Log.d(TAG, "Fetching child layout: " + type);
}
if (listener != null) {
return listener.onLayoutRequired(type, parent);
}
return null;
}
public void registerFormatter(Formatter formatter) {
this.formatter.put(formatter.getName(), formatter);
}
public void unregisterFormatter(String formatterName) {
if (Formatter.NOOP.getName().equals(formatterName)) {
return;
}
this.formatter.remove(formatterName);
}
public Formatter getFormatter(String formatterName) {
return this.formatter.get(formatterName);
}
}