package org.itsnat.droid.impl.browser.serveritsnat;
import org.itsnat.droid.ItsNatDroidException;
import org.itsnat.droid.impl.browser.FragmentLayoutInserter;
import org.itsnat.droid.impl.browser.HttpRequestData;
import org.itsnat.droid.impl.browser.XMLDOMLayoutPageDownloader;
import org.itsnat.droid.impl.dom.DOMAttrRemote;
import org.itsnat.droid.impl.dom.ParsedResource;
import org.itsnat.droid.impl.dom.layout.XMLDOMLayoutPage;
import org.itsnat.droid.impl.dom.layout.XMLDOMLayoutPageItsNat;
import org.itsnat.droid.impl.domparser.XMLDOMParserContext;
import org.itsnat.droid.impl.util.MiscUtil;
import org.itsnat.droid.impl.util.NamespaceUtil;
import org.itsnat.droid.impl.util.StringUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
/**
* Created by jmarranz on 25/01/2016.
*/
public class XMLDOMLayoutPageItsNatDownloader extends XMLDOMLayoutPageDownloader
{
protected XMLDOMLayoutPageItsNatDownloader(XMLDOMLayoutPageItsNat xmlDOM,String pageURLBase, HttpRequestData httpRequestData, String itsNatServerVersion,
Map<String,ParsedResource> urlResDownloadedMap,XMLDOMParserContext xmlDOMParserContext)
{
super(xmlDOM,pageURLBase,httpRequestData,itsNatServerVersion,urlResDownloadedMap,xmlDOMParserContext);
}
public static XMLDOMLayoutPageItsNatDownloader createXMLDOMLayoutPageItsNatDownloader(XMLDOMLayoutPageItsNat xmlDOM,String pageURLBase, HttpRequestData httpRequestData,
String itsNatServerVersion,Map<String,ParsedResource> urlResDownloadedMap,XMLDOMParserContext xmlDOMParserContext)
{
return new XMLDOMLayoutPageItsNatDownloader(xmlDOM,pageURLBase,httpRequestData,itsNatServerVersion,urlResDownloadedMap,xmlDOMParserContext);
}
public XMLDOMLayoutPageItsNat getXMLDOMLayoutPageItsNat()
{
return (XMLDOMLayoutPageItsNat)xmlDOM;
}
public LinkedList<DOMAttrRemote> parseBeanShellAndDownloadRemoteResources(String code) throws Exception
{
@SuppressWarnings("unchecked") LinkedList<DOMAttrRemote>[] attrRemoteListBSParsed = new LinkedList[1];
@SuppressWarnings("unchecked") LinkedList<String>[] classNameListBSParsed = new LinkedList[1];
@SuppressWarnings("unchecked") LinkedList<String>[] xmlMarkupListBSParsed = new LinkedList[1];
//XMLDOMLayoutPageItsNat xmlDOMLayoutPageItsNat = getXMLDOMLayoutPageItsNat();
parseBSRemoteAttribs(code, attrRemoteListBSParsed, classNameListBSParsed, xmlMarkupListBSParsed);
if (attrRemoteListBSParsed[0] != null)
{
// llena los elementos de DOMAttrRemote attrRemoteList con el recurso descargado que le corresponde
downloadRemoteAttrResources(attrRemoteListBSParsed[0]);
}
if (classNameListBSParsed[0] != null)
{
XMLDOMLayoutPage[] xmlDOMLayoutPageArr = wrapAndParseMarkupFragment(classNameListBSParsed[0], xmlMarkupListBSParsed[0]);
for (XMLDOMLayoutPage xmlDOM : xmlDOMLayoutPageArr)
{
XMLDOMLayoutPageDownloader downloader = XMLDOMLayoutPageDownloader.createXMLDOMLayoutPageDownloader(xmlDOM,pageURLBase, httpRequestData,itsNatServerVersion,urlResDownloadedMap,xmlDOMParserContext);
downloader.downloadRemoteResources();
}
}
return attrRemoteListBSParsed[0]; // Puede ser null
}
private XMLDOMLayoutPage[] wrapAndParseMarkupFragment(LinkedList<String> classNameListBSParsed, LinkedList<String> xmlMarkupListBSParsed)
{
XMLDOMLayoutPage[] xmlDOMLayoutPageFragmentArr = new XMLDOMLayoutPage[classNameListBSParsed.size()];
if (classNameListBSParsed.size() != xmlMarkupListBSParsed.size()) throw MiscUtil.internalError();
// Así se cachea pero sobre to_do se cargan los recursos remotos dentro del markup que trae el setInnerXML()
XMLDOMLayoutPageItsNat xmlDOMLayoutPageParent = getXMLDOMLayoutPageItsNat();
Iterator<String> itClassName = classNameListBSParsed.iterator();
Iterator<String> itMarkup = xmlMarkupListBSParsed.iterator();
int i = 0;
while(itClassName.hasNext())
{
String className = itClassName.next();
String markup = itMarkup.next();
XMLDOMLayoutPage xmlDOMFragment = FragmentLayoutInserter.wrapAndParseMarkupFragment(className, markup,xmlDOMLayoutPageParent,itsNatServerVersion,xmlDOMParserContext);
xmlDOMLayoutPageFragmentArr[i] = xmlDOMFragment;
}
return xmlDOMLayoutPageFragmentArr;
}
private void parseBSRemoteAttribs(String code,LinkedList<DOMAttrRemote>[] attrRemoteList,LinkedList<String>[] classNameList,LinkedList<String>[] xmlMarkupList)
{
// Caso 1: setAttributeNS y setAttribute
// Ej. de formato esperado: /*[s*/NSAND,"textSize","@remote:dimen/droid/res/values/test_values_remote.xml:test_dimen_textSize"/*s]*/
// /*[s*/"style","@remote:dimen/droid/res/values/test_values_remote.xml:test_style"/*s]*/
// Caso 2: setAttrBatch
// Ej. de formato esperado /*[n*/null/*n]*/ /*[k*/"style"/*k]*/... /*[v*/"@remote:style/droid/res/values/test_values_remote.xml:test_style_remote"/*v]*/...
// En vez de null (namespace) puede ser NSAND o "some namespace" o null
// Caso 3: setInnerXML
// Ej. de formato esperado /*[i*/"className","xml serialized"/*i]*/
while(true)
{
code = gotoNextCase(code);
if (code == null)
break; // No hay más
if (code.startsWith("/*[s*/"))
{
if (attrRemoteList[0] == null) attrRemoteList[0] = new LinkedList<DOMAttrRemote>();
code = processCase1(code,attrRemoteList[0]);
}
else if (code.startsWith("/*[n*/"))
{
if (attrRemoteList[0] == null) attrRemoteList[0] = new LinkedList<DOMAttrRemote>();
code = processCase2(code, attrRemoteList[0]);
}
else if (code.startsWith("/*[i*/"))
{
if (classNameList[0] == null) classNameList[0] = new LinkedList<String>();
if (xmlMarkupList[0] == null) xmlMarkupList[0] = new LinkedList<String>();
code = processCase3(code,classNameList[0], xmlMarkupList[0]);
}
}
}
private String gotoNextCase(String code)
{
int lenDelimiter = "/*[s*/".length(); // Mismo valor para todos los demás casos incluidos los sufijos
while(true)
{
int posPrefix = code.indexOf("/*[");
int len = code.length() - posPrefix;
if (len < lenDelimiter) // Falso positivo, no hay suficientes caracteres ni siquiera para analizar el prefijo
return null; // NO hay más
// Tenemos la garantía de que hay caracteres suficientes para analizar el prefijo
int pos = posPrefix + "/*[".length();
char letter = code.charAt(pos);
pos++;
char asterisk = code.charAt(pos);
pos++;
char close = code.charAt(pos);
if ((letter != 's' && letter != 'n' && letter != 'i') || asterisk != '*' || close != '/')
{
// No es un prefijo correcto
if (pos == code.length() - 1)
return null; // Es el último caracter, no hay más
code = code.substring(pos + 1);
continue;
}
return code.substring(posPrefix);
}
}
private String processCase1(String code,LinkedList<DOMAttrRemote> attrRemoteList)
{
// Ej. de formato esperado: /*[s*/NSAND,"textSize","@remote:dimen/droid/res/values/test_values_remote.xml:test_dimen_textSize"/*s]*/
// /*[s*/"style","@remote:dimen/droid/res/values/test_values_remote.xml:test_style"/*s]*/
int lenDelimiter = "/*[s*/".length(); // idem que el sufijo "/*s]*/"
int posEnd = code.indexOf("/*s]*/", lenDelimiter);
if (posEnd != -1)
{
String attrCode = code.substring(lenDelimiter, posEnd);
DOMAttrRemote domAttr = parseSingleRemoteAttr(attrCode);
attrRemoteList.add(domAttr);
code = code.substring(posEnd + lenDelimiter);
}
else
{
throw new ItsNatDroidException("Unexpected format " + code); // JODER que raro, no hay terminador, o es un bug o un intento de liarla por parte del programador final, salimos, está
}
return code;
}
private String processCase2(String code,LinkedList<DOMAttrRemote> attrRemoteList)
{
// Ej. de formato esperado /*[n*/null/*n]*/ /*[k*/"style"/*k]*/... /*[v*/"@remote:style/droid/res/values/test_values_remote.xml:test_style_remote"/*v]*/...
// En vez de null (namespace) puede ser NSAND o "some namespace" o null
int lenDelimiter = "/*[n*/".length(); // idem que el sufijo "/*n]*/" e idem con k y v
String namespaceURI;
{
int posEnd = code.indexOf("/*n]*/", lenDelimiter);
if (posEnd != -1)
{
String namespaceCode = code.substring(lenDelimiter, posEnd);
namespaceURI = parseNamespaceURI(namespaceCode);
code = code.substring(posEnd + lenDelimiter);
}
else
{
throw new ItsNatDroidException("Unexpected format " + code); // JODER que raro, no hay terminador, o es un bug o un intento de liarla por parte del programador final
}
}
// Atributos que tienen en común el namespace anteriormente obtenido
int posFirstValue = code.indexOf("/*[v*/"); // No debemos buscar más allá de este punto porque estaremos encontrando los key de otra sentencia
if (posFirstValue == -1) throw new ItsNatDroidException("Unexpected format " + code);
ArrayList<String> nameList = null;
while (true)
{
// name
{
int posOpenKey = code.indexOf("/*[k*/");
if (posOpenKey != -1)
{
if (posOpenKey < posFirstValue)
{
int posEnd = code.indexOf("/*k]*/", posOpenKey + lenDelimiter);
if (posEnd != -1)
{
String nameCode = code.substring(posOpenKey + lenDelimiter, posEnd);
String name = parseAttrName(nameCode);
if (nameList == null) nameList = new ArrayList<String>();
nameList.add(name);
code = code.substring(posEnd + lenDelimiter);
}
else
{
throw new ItsNatDroidException("Unexpected format " + code); // JODER que raro, no hay terminador, o es un bug o un intento de liarla por parte del programador final
}
}
else
{
break; // No hay más
}
}
else
{
break; // No hay más
}
}
}
if (nameList == null) throw new ItsNatDroidException("Unexpected format " + code); // Si se encontró un metadato namespace es porque esperamos AL MENOS un atributo remoto
int countExpected = nameList.size();
ArrayList<String> valueList = new ArrayList<String>(countExpected);
for(int i = 0; i < countExpected; i++)
{
// value
{
int posOpenValue = code.indexOf("/*[v*/");
if (posOpenValue != -1)
{
int posEnd = code.indexOf("/*v]*/", posOpenValue + lenDelimiter);
if (posEnd != -1)
{
String valueCode = code.substring(posOpenValue + lenDelimiter, posEnd);
String value = extractStringLiteralContent(valueCode);
if (valueList == null) valueList = new ArrayList<String>();
valueList.add(value);
code = code.substring(posEnd + lenDelimiter);
}
else
{
throw new ItsNatDroidException("Unexpected format " + code); // JODER que raro, no hay terminador, o es un bug o un intento de liarla por parte del programador final
}
}
else
{
throw new ItsNatDroidException("Unexpected format " + code); // Esperamos un número dado de values que conocemos de antemano
}
}
}
if (nameList.size() != valueList.size()) throw new ItsNatDroidException("Unexpected format " + code);
int count = nameList.size();
for(int i = 0; i < count; i++)
{
String name = nameList.get(i);
String value = valueList.get(i);
DOMAttrRemote domAttr = createDOMAttrRemote(namespaceURI, name, value);
attrRemoteList.add(domAttr);
}
return code;
}
private String processCase3(String code,LinkedList<String> classNameList,LinkedList<String> xmlMarkupList)
{
// Caso 3: setInnerXML
// Ej. de formato esperado /*[i*/"className","xml serialized"/*i]*/
int lenDelimiter = "/*[i*/".length(); // idem que el sufijo "/*i]*/"
int posEnd = code.indexOf("/*i]*/", lenDelimiter);
if (posEnd != -1)
{
String classNameCommAndxmlMarkupStrLiteral = code.substring(lenDelimiter, posEnd);
// No usamos split(",") porque el markup puede contener comas (puede haber de to_do excepto los caracteres escapados necesarios para meter en "")
// Lo seguro es que el className no tiene comas
int posComma = classNameCommAndxmlMarkupStrLiteral.indexOf(',');
if (posComma == -1)
throw new ItsNatDroidException("Unexpected format " + classNameCommAndxmlMarkupStrLiteral);
String classNameStrLiteral = classNameCommAndxmlMarkupStrLiteral.substring(0,posComma);
String className = extractStringLiteralContent(classNameStrLiteral);
classNameList.add(className);
String xmlMarkupStrLiteral = classNameCommAndxmlMarkupStrLiteral.substring(posComma + 1);
String xmlMarkup = extractStringLiteralContent(xmlMarkupStrLiteral);
xmlMarkup = StringUtil.convertEscapedStringLiteralToNormalString(xmlMarkup);
xmlMarkupList.add(xmlMarkup);
code = code.substring(posEnd + lenDelimiter);
}
else
{
throw new ItsNatDroidException("Unexpected format " + code); // JODER que raro, no hay terminador, o es un bug o un intento de liarla por parte del programador final, salimos, está
}
return code;
}
private DOMAttrRemote parseSingleRemoteAttr(String code)
{
// Ej. de formato esperado:
// NSAND,"textSize","@remote:dimen/droid/res/values/test_values_remote.xml:test_dimen_textSize"
// "android:textSize","@remote:dimen/droid/res/values/test_values_remote.xml:test_dimen_textSize"
// "style","@remote:dimen/droid/res/values/test_values_remote.xml:test_style"
// En lugar de NSAND puede ser un "some namespace" o null
String[] attrParts = code.split(","); // No hay riesgo de que el namespace o el name o el value tengan alguna coma
if (attrParts.length < 2 || attrParts.length > 3) throw new ItsNatDroidException("Unexpected format: " + code);
int part = 0;
String namespaceURI = null;
if (attrParts.length == 3)
{
namespaceURI = parseNamespaceURI(attrParts[part]);
part++;
}
String name = attrParts[part];
name = parseAttrName(name);
part++;
String value = attrParts[part];
value = extractStringLiteralContent(value); // Aunque es un valor entre comillas, esperamos un "@remote:path:name", no esperamos un texto normal literal que podría tener " o \n al serializar como string literal
return createDOMAttrRemote(namespaceURI,name,value);
}
private DOMAttrRemote createDOMAttrRemote(String namespaceURI,String name,String value)
{
XMLDOMLayoutPageItsNat xmlDOMLayoutPageItsNat = getXMLDOMLayoutPageItsNat();
return (DOMAttrRemote)xmlDOMLayoutPageItsNat.createDOMAttrNotSyncResource(namespaceURI, name, value);
}
private static String parseNamespaceURI(String code)
{
String namespaceURI = code;
if (NamespaceUtil.XMLNS_ANDROID_ALIAS.equals(namespaceURI)) // la constante NSAND
namespaceURI = NamespaceUtil.XMLNS_ANDROID;
else if ("null".equals(namespaceURI))
namespaceURI = null;
else
namespaceURI = extractStringLiteralContent(namespaceURI);
return namespaceURI;
}
private static String parseAttrName(String code)
{
String name = extractStringLiteralContent(code);
return name;
}
private static String extractStringLiteralContent(String code)
{
if (!code.startsWith("\"")) throw new ItsNatDroidException("Unexpected format: " + code);
if (!code.endsWith("\"")) throw new ItsNatDroidException("Unexpected format: " + code);
code = code.substring(1,code.length()-1);
return code;
}
}