LayoutFileParser.java revision 0cb9fbb96197af013f4f879ed6cddf2681b88fd6
13bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar/*
23bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Copyright (C) 2015 The Android Open Source Project
33bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
43bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * you may not use this file except in compliance with the License.
53bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * You may obtain a copy of the License at
63bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
73bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Unless required by applicable law or agreed to in writing, software
83bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
93bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
103bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * See the License for the specific language governing permissions and
113bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * limitations under the License.
123bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar */
133bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
14fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountpackage android.databinding.tool.store;
153bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
163bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport com.google.common.base.Preconditions;
173bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
183bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.apache.commons.io.FileUtils;
19895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyarimport org.apache.commons.lang3.ObjectUtils;
203bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.apache.commons.lang3.StringUtils;
213bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.Document;
223bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.NamedNodeMap;
233bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.Node;
243bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.NodeList;
253bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.xml.sax.SAXException;
263bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
27fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.L;
28fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.ParserHelper;
29fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.XmlEditor;
30fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mount
313bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.io.File;
323bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.io.IOException;
3375da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyarimport java.net.MalformedURLException;
3475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyarimport java.net.URISyntaxException;
3575da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyarimport java.net.URL;
363bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.util.ArrayList;
3796e1c821dd446d1ed78f8ae61131550588f60a24George Mountimport java.util.HashMap;
383bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.util.List;
393bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
403bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.DocumentBuilder;
413bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.DocumentBuilderFactory;
423bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.ParserConfigurationException;
433bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPath;
443bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathConstants;
453bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathExpression;
463bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathExpressionException;
473bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathFactory;
483bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
493bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar/**
503bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Gets the list of XML files and creates a list of
51fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mount * {@link android.databinding.tool.store.ResourceBundle} that can be persistent or converted to
523bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * LayoutBinder.
533bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar */
543bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarpublic class LayoutFileParser {
553b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_VARIABLE_DEFINITIONS = "/layout/data/variable";
563b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_BINDING_ELEMENTS = "/layout/*[name() != 'data' and name() != 'merge'] | /layout/merge/* | //*[include/.. or @*[starts-with(., '@{') and substring(., string-length(.)) = '}']]";
5795d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount    private static final String XPATH_ID_ELEMENTS = "//*[@*[local-name()='id']]";
583b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_IMPORT_DEFINITIONS = "/layout/data/import";
593b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_MERGE_ELEMENT = "/layout/merge";
603b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_BINDING_LAYOUT = "/layout";
613b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_BINDING_CLASS = "/layout/data/@class";
623bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    final String LAYOUT_PREFIX = "@layout/";
633bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
644d4979490e1fa374c0d7f3599fed0a9e83a579d0George Mount    public ResourceBundle.LayoutFileBundle parseXml(File xml, String pkg, int minSdk)
653bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            throws ParserConfigurationException, IOException, SAXException,
663bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            XPathExpressionException {
670cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        final String xmlNoExtension = ParserHelper.stripExtension(xml.getName());
680390898cf7c4fcad255e8cfd6802f722b516cb2cGeorge Mount        final String newTag = xml.getParentFile().getName() + '/' + xmlNoExtension;
690390898cf7c4fcad255e8cfd6802f722b516cb2cGeorge Mount        File original = stripFileAndGetOriginal(xml, newTag);
703bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (original == null) {
71895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyar            L.d("assuming the file is the original for %s", xml.getAbsoluteFile());
72895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyar            original = xml;
733bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
743bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("parsing file %s", xml.getAbsolutePath());
753bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
763bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
773bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final DocumentBuilder builder = factory.newDocumentBuilder();
788e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount        final Document doc = builder.parse(original);
793bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
803bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final XPathFactory xPathFactory = XPathFactory.newInstance();
813bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final XPath xPath = xPathFactory.newXPath();
823bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
833b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        if (!isBindingLayout(doc, xPath)) {
843b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            return null;
853b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
863b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
873bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        List<Node> variableNodes = getVariableNodes(doc, xPath);
8896e1c821dd446d1ed78f8ae61131550588f60a24George Mount        final List<Node> imports = getImportNodes(doc, xPath);
8996e1c821dd446d1ed78f8ae61131550588f60a24George Mount
9096e1c821dd446d1ed78f8ae61131550588f60a24George Mount        ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(
9196e1c821dd446d1ed78f8ae61131550588f60a24George Mount                xmlNoExtension, xml.getParentFile().getName(), pkg, isMerge(doc, xPath));
923bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
933bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("number of variable nodes %d", variableNodes.size());
943bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        for (Node item : variableNodes) {
953bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("reading variable node %s", item);
963bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            NamedNodeMap attributes = item.getAttributes();
973bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            String variableName = attributes.getNamedItem("name").getNodeValue();
983bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            String variableType = attributes.getNamedItem("type").getNodeValue();
993bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("name: %s, type:%s", variableName, variableType);
1003bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            bundle.addVariable(variableName, variableType);
1013bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
1023bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
1033bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("import node count %d", imports.size());
1043bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        for (Node item : imports) {
1053bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            NamedNodeMap attributes = item.getAttributes();
1063bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            String type = attributes.getNamedItem("type").getNodeValue();
1073bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            final Node aliasNode = attributes.getNamedItem("alias");
1083bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            final String alias;
1093bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            if (aliasNode == null) {
1103bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                final String[] split = StringUtils.split(type, '.');
1113bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                alias = split[split.length - 1];
1123bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            } else {
1133bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                alias = aliasNode.getNodeValue();
1143bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            }
1153bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            bundle.addImport(alias, type);
1163bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
1173bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
1183b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        final Node layoutParent = getLayoutParent(doc, xPath);
1193bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final List<Node> bindingNodes = getBindingNodes(doc, xPath);
12096e1c821dd446d1ed78f8ae61131550588f60a24George Mount        final HashMap<Node, String> nodeTagMap = new HashMap<Node, String>();
1213bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("number of binding nodes %d", bindingNodes.size());
1223b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        int tagNumber = 0;
1233bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        for (Node parent : bindingNodes) {
1243bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            NamedNodeMap attributes = parent.getAttributes();
1258e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            String nodeName = parent.getNodeName();
1267ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount            String viewName = null;
1278e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            String includedLayoutName = null;
1288e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            final Node id = attributes.getNamedItem("android:id");
12996e1c821dd446d1ed78f8ae61131550588f60a24George Mount            final String tag;
1304d4979490e1fa374c0d7f3599fed0a9e83a579d0George Mount            final Node originalTag = attributes.getNamedItem("android:tag");
1318e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            if ("include".equals(nodeName)) {
1328e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                // get the layout attribute
1338e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                final Node includedLayout = attributes.getNamedItem("layout");
1348e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                Preconditions.checkNotNull(includedLayout, "must include a layout");
1358e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                final String includeValue = includedLayout.getNodeValue();
1368e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                Preconditions.checkArgument(includeValue.startsWith(LAYOUT_PREFIX));
1378e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                // if user is binding something there, there MUST be a layout file to be
1388e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                // generated.
1398e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                String layoutName = includeValue.substring(LAYOUT_PREFIX.length());
1408e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                includedLayoutName = layoutName;
14196e1c821dd446d1ed78f8ae61131550588f60a24George Mount                tag = nodeTagMap.get(parent.getParentNode());
1424d4979490e1fa374c0d7f3599fed0a9e83a579d0George Mount            } else if ("fragment".equals(nodeName)) {
143d6527ee28cc3aa05818799af8def9593346f91bcGeorge Mount                L.e("fragments do not support data binding expressions: %s", xml.getPath());
144d6527ee28cc3aa05818799af8def9593346f91bcGeorge Mount                continue;
1453bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            } else {
1467ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount                viewName = getViewName(parent);
1473b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount                if (layoutParent == parent.getParentNode()) {
1483b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount                    tag = newTag + "_" + tagNumber;
14996e1c821dd446d1ed78f8ae61131550588f60a24George Mount                } else {
1507ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount                    tag = "binding_" + tagNumber;
15196e1c821dd446d1ed78f8ae61131550588f60a24George Mount                }
1523b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount                tagNumber++;
1538e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            }
1548e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            final ResourceBundle.BindingTargetBundle bindingTargetBundle =
1558e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                    bundle.createBindingTarget(id == null ? null : id.getNodeValue(),
1567ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount                            viewName, true, tag, originalTag == null ? null : originalTag.getNodeValue());
15796e1c821dd446d1ed78f8ae61131550588f60a24George Mount            nodeTagMap.put(parent, tag);
1588e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            bindingTargetBundle.setIncludedLayout(includedLayoutName);
1598e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount
1608e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            final int attrCount = attributes.getLength();
1618e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            for (int i = 0; i < attrCount; i ++) {
1628e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                final Node attr = attributes.item(i);
1638e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                String value = attr.getNodeValue();
1648e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                if (value.charAt(0) == '@' && value.charAt(1) == '{' &&
1658e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                        value.charAt(value.length() - 1) == '}') {
1668e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                    final String strippedValue = value.substring(2, value.length() - 1);
1678e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                    bindingTargetBundle.addBinding(attr.getNodeName(), strippedValue);
1688e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                }
1693bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            }
1703bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
1713bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
17296e1c821dd446d1ed78f8ae61131550588f60a24George Mount        final List<Node> idNodes = getNakedIds(doc, xPath);
17396e1c821dd446d1ed78f8ae61131550588f60a24George Mount        for (Node node : idNodes) {
174d6527ee28cc3aa05818799af8def9593346f91bcGeorge Mount            if (!bindingNodes.contains(node) && !"include".equals(node.getNodeName()) &&
175d6527ee28cc3aa05818799af8def9593346f91bcGeorge Mount                    !"fragment".equals(node.getNodeName())) {
17696e1c821dd446d1ed78f8ae61131550588f60a24George Mount                final Node id = node.getAttributes().getNamedItem("android:id");
1777ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount                final String className = getViewName(node);
17896e1c821dd446d1ed78f8ae61131550588f60a24George Mount                bundle.createBindingTarget(id.getNodeValue(), className, true, null, null);
17995d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount            }
18095d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        }
18195d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount
1823b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        bundle.setBindingClass(getBindingClass(doc, xPath, original));
1833b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
1843bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return bundle;
1853bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
1863bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
1873b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private boolean isBindingLayout(Document doc, XPath xPath) throws XPathExpressionException {
1883b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        return !get(doc, xPath, XPATH_BINDING_LAYOUT).isEmpty();
1893b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    }
1903b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
1913b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private Node getLayoutParent(Document doc, XPath xPath) throws XPathExpressionException {
1923b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        if (isMerge(doc, xPath)) {
1933b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            return get(doc, xPath, XPATH_MERGE_ELEMENT).get(0);
1943b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        } else {
1953b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            return get(doc, xPath, XPATH_BINDING_LAYOUT).get(0);
1963b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
1973b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    }
1983b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
1993b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private String getBindingClass(Document doc, XPath xPath, File file)
2003b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            throws XPathExpressionException {
2013b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        List<Node> nodes = get(doc, xPath, XPATH_BINDING_CLASS);
2023b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        if (nodes.isEmpty()) {
2033b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            return null;
2043b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
2053b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        if (nodes.size() > 1) {
2063b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            L.e("More than one binding class declared in %s", file.getAbsolutePath());
2073b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
2083b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        return nodes.get(0).getNodeValue();
2093b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    }
2103b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
2113bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private List<Node> getBindingNodes(Document doc, XPath xPath) throws XPathExpressionException {
2123bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return get(doc, xPath, XPATH_BINDING_ELEMENTS);
2133bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2143bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2153bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private List<Node> getVariableNodes(Document doc, XPath xPath) throws XPathExpressionException {
2163bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return get(doc, xPath, XPATH_VARIABLE_DEFINITIONS);
2173bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2183bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2193bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private List<Node> getImportNodes(Document doc, XPath xPath) throws XPathExpressionException {
2203bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return get(doc, xPath, XPATH_IMPORT_DEFINITIONS);
2213bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2223bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
22395d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount    private List<Node> getNakedIds(Document doc, XPath xPath) throws XPathExpressionException {
22495d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        return get(doc, xPath, XPATH_ID_ELEMENTS);
22595d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount    }
22695d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount
22796e1c821dd446d1ed78f8ae61131550588f60a24George Mount    private boolean isMerge(Document doc, XPath xPath) throws XPathExpressionException {
22896e1c821dd446d1ed78f8ae61131550588f60a24George Mount        return !get(doc, xPath, XPATH_MERGE_ELEMENT).isEmpty();
22996e1c821dd446d1ed78f8ae61131550588f60a24George Mount    }
23096e1c821dd446d1ed78f8ae61131550588f60a24George Mount
2313bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private List<Node> get(Document doc, XPath xPath, String pattern)
2323bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            throws XPathExpressionException {
2333bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final XPathExpression expr = xPath.compile(pattern);
2343bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return toList((NodeList) expr.evaluate(doc, XPathConstants.NODESET));
2353bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2363bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2373bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private List<Node> toList(NodeList nodeList) {
238895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyar        List<Node> result = new ArrayList<Node>();
2393bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        for (int i = 0; i < nodeList.getLength(); i ++) {
2403bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            result.add(nodeList.item(i));
2413bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
2423bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return result;
2433bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2443bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2457ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount    private String getViewName(Node viewNode) {
24695d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        String viewName = viewNode.getNodeName();
24795d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        if ("view".equals(viewName)) {
24895d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount            Node classNode = viewNode.getAttributes().getNamedItem("class");
24995d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount            if (classNode == null) {
25095d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount                L.e("No class attribute for 'view' node");
25195d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount            } else {
25295d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount                viewName = classNode.getNodeValue();
25395d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount            }
25495d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        }
2553bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return viewName;
2563bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2573bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2583bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private void stripBindingTags(File xml, String newTag) throws IOException {
2590cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String res = XmlEditor.strip(xml, newTag);
2603bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (res != null) {
2613bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("file %s has changed, overwriting %s", xml.getName(), xml.getAbsolutePath());
2623bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            FileUtils.writeStringToFile(xml, res);
2633bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
2643bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2653bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2663bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private File stripFileAndGetOriginal(File xml, String binderId)
2673bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            throws ParserConfigurationException, IOException, SAXException,
2683bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            XPathExpressionException {
2698e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount        L.d("parsing resource file %s", xml.getAbsolutePath());
2703bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
2713bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        DocumentBuilder builder = factory.newDocumentBuilder();
2723bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        Document doc = builder.parse(xml);
2733bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        XPathFactory xPathFactory = XPathFactory.newInstance();
2743bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        XPath xPath = xPathFactory.newXPath();
2753bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final XPathExpression commentElementExpr = xPath
2763bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                .compile("//comment()[starts-with(., \" From: file:\")][last()]");
2773bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final NodeList commentElementNodes = (NodeList) commentElementExpr
2783bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                .evaluate(doc, XPathConstants.NODESET);
2793bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("comment element nodes count %s", commentElementNodes.getLength());
2803bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (commentElementNodes.getLength() == 0) {
2813bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("cannot find comment element to find the actual file");
2823bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            return null;
2833bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
2843bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final Node first = commentElementNodes.item(0);
28575da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        String actualFilePath = first.getNodeValue().substring(" From:".length()).trim();
2863bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("actual file to parse: %s", actualFilePath);
28775da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        File actualFile = urlToFile(new java.net.URL(actualFilePath));
2883bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (!actualFile.canRead()) {
2893bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("cannot find original, skipping. %s", actualFile.getAbsolutePath());
2903bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            return null;
2913bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
2923bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
2933bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        // now if file has any binding expressions, find and delete them
2943bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        // TODO we should rely on namespace to avoid parsing file twice
2953b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        boolean changed = isBindingLayout(doc, xPath);
2963bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (changed) {
2973bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            stripBindingTags(xml, binderId);
2983bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
2993bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return actualFile;
3003bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
30175da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar
30275da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    public static File urlToFile(String url) throws MalformedURLException {
30375da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        return urlToFile(new URL(url));
30475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    }
30575da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar
30675da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    public static File urlToFile(URL url) throws MalformedURLException {
30775da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        try {
30875da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            return new File(url.toURI());
30975da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        }
31075da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        catch (IllegalArgumentException e) {
31175da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            MalformedURLException ex = new MalformedURLException(e.getLocalizedMessage());
31275da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            ex.initCause(e);
31375da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            throw ex;
31475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        }
31575da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        catch (URISyntaxException e) {
31675da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            return new File(url.getPath());
31775da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        }
31875da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    }
3193bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar}
320