Loading tools/data-binding/TestApp/src/main/java/com/android/databinding/testapp/TestActivity.java +7 −0 Original line number Diff line number Diff line Loading @@ -14,7 +14,14 @@ package com.android.databinding.testapp; import android.app.Activity; import android.os.Bundle; import android.view.WindowManager; public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } tools/data-binding/compiler/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ buildscript { dependencies { compile 'junit:junit:4.12' compile 'org.apache.commons:commons-lang3:3.3.2' compile 'org.apache.commons:commons-io:1.3.2' compile 'com.google.guava:guava:18.0' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" compile project(":baseLibrary") Loading tools/data-binding/compiler/src/main/java/com/android/databinding/BindingTarget.java +12 −27 Original line number Diff line number Diff line Loading @@ -20,71 +20,56 @@ import com.android.databinding.expr.Expr; import com.android.databinding.expr.ExprModel; import com.android.databinding.reflection.ReflectionAnalyzer; import com.android.databinding.reflection.ReflectionClass; import com.android.databinding.store.ResourceBundle; import java.util.ArrayList; import java.util.List; public class BindingTarget { String mId; String mViewClass; List<Binding> mBindings = new ArrayList<>(); ExprModel mModel; ReflectionClass mResolvedClass; String mIncludedLayout; // if this target presents itself in multiple layout files with different view types, // it receives an interface type and should use it in the getter instead. String mInterfaceType; // if this target is inherited from a common layout interface, used is false so that we don't // create find view by id etc for it. boolean mUsed; public BindingTarget(String id, String viewClass, boolean used) { mId = id; mViewClass = viewClass; mUsed = used; private ResourceBundle.BindingTargetBundle mBundle; public BindingTarget(ResourceBundle.BindingTargetBundle bundle) { mBundle = bundle; } public boolean isUsed() { return mUsed; return mBundle.isUsed(); } public void addBinding(String name, Expr expr) { mBindings.add(new Binding(this, name, expr)); } public void setInterfaceType(String interfaceType) { mInterfaceType = interfaceType; } public String getInterfaceType() { return mInterfaceType == null ? mViewClass : mInterfaceType; return mBundle.getInterfaceType() == null ? mBundle.getFullClassName() : mBundle.getInterfaceType(); } public String getId() { return mId; return mBundle.getId(); } public String getViewClass() { return mViewClass; return mBundle.getFullClassName(); } public ReflectionClass getResolvedType() { if (mResolvedClass == null) { mResolvedClass = ReflectionAnalyzer.getInstance().findClass(mViewClass); mResolvedClass = ReflectionAnalyzer.getInstance().findClass(mBundle.getFullClassName()); } return mResolvedClass; } public String getIncludedLayout() { return mIncludedLayout; return mBundle.getIncludedLayout(); } public boolean isBinder() { return mIncludedLayout != null; } public void setIncludedLayout(String includedLayout) { mIncludedLayout = includedLayout; return getIncludedLayout() != null; } public List<Binding> getBindings() { Loading tools/data-binding/compiler/src/main/java/com/android/databinding/CompilerChef.java 0 → 100644 +153 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * 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.android.databinding; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.android.databinding.store.LayoutFileParser; import com.android.databinding.store.ResourceBundle; import com.android.databinding.util.L; import com.android.databinding.writer.DataBinderWriter; import com.android.databinding.writer.FileWriter; import com.android.databinding.writer.FileWriterImpl; import org.xml.sax.SAXException; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpressionException; /** * Chef class for compiler. * * Different build systems can initiate a version of this to handle their work */ public class CompilerChef { private FileWriter mFileWriter = new FileWriterImpl(); private LayoutFileParser mLayoutFileParser; private ResourceBundle mResourceBundle; private DataBinder mDataBinder; private File mOutputBaseDir; private String mAppPackage; private List<File> mResourceFolders; public void setupForParsing(String appPkg, List<File> resourceFolders, File codegenTargetFolder) { mOutputBaseDir = codegenTargetFolder; mResourceFolders = resourceFolders; mAppPackage = appPkg; } public void processResources() throws ParserConfigurationException, SAXException, XPathExpressionException, IOException { if (mResourceBundle != null) { return; // already processed } mResourceBundle = new ResourceBundle(); mResourceBundle.setAppPackage(mAppPackage); mLayoutFileParser = new LayoutFileParser(); int layoutId = 0; for (File resFolder : Iterables.filter(mResourceFolders, fileExists)) { for (File layoutFolder : resFolder.listFiles(layoutFolderFilter)) { for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) { final ResourceBundle.LayoutFileBundle bundle = mLayoutFileParser .parseXml(xmlFile, mAppPackage, layoutId); if (bundle != null && !bundle.isEmpty()) { mResourceBundle.addLayoutBundle(bundle, layoutId); layoutId ++; } } } } mResourceBundle.validateMultiResLayouts(); } public void ensureDataBinder() { if (mDataBinder == null) { mDataBinder = new DataBinder(mResourceBundle); mDataBinder.setFileWriter(mFileWriter); } } public boolean hasAnythingToGenerate() { L.d("checking if we have anything to genreate. bundle size: %s", mResourceBundle == null ? -1 : mResourceBundle.getLayoutBundles().size()); return mResourceBundle != null && mResourceBundle.getLayoutBundles().size() > 0; } public void writeDbrFile(File folder) { ensureDataBinder(); final String pkg = "com.android.databinding.library"; DataBinderWriter dbr = new DataBinderWriter(pkg, mResourceBundle.getAppPackage(), "GeneratedDataBinderRenderer", mDataBinder.getLayoutBinders()); if (folder == null) { folder = new File(mOutputBaseDir.getAbsolutePath() + "/" + pkg.replace('.','/')); } folder.mkdirs(); if (dbr.getLayoutBinders().size() > 0) { mFileWriter.writeToFile(new File(folder, dbr.getClassName() + ".java"), dbr.write()); } } public void setOutputBaseDir(File baseDir) { mOutputBaseDir = baseDir; } public void writeViewBinderInterfaces(File folder) { ensureDataBinder(); if (folder == null) { folder = new File(mOutputBaseDir.getAbsolutePath() + "/" + mResourceBundle.getAppPackage().replace( '.', '/') + "/generated"); } mDataBinder.writerBinderInterfaces(folder); } public void writeViewBinders(File folder) { ensureDataBinder(); if (folder == null) { folder = new File(mOutputBaseDir.getAbsolutePath() + "/" + mResourceBundle.getAppPackage().replace( '.', '/') + "/generated"); } mDataBinder.writeBinders(folder); } private final Predicate<File> fileExists = new Predicate<File>() { @Override public boolean apply(File input) { return input.exists() && input.canRead(); } }; private final FilenameFilter layoutFolderFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith("layout"); } }; private final FilenameFilter xmlFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".xml"); } }; } tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java +37 −148 Original line number Diff line number Diff line Loading @@ -15,176 +15,65 @@ */ package com.android.databinding; import com.google.common.base.Preconditions; import com.android.databinding.store.ResourceBundle; import com.android.databinding.util.L; import com.android.databinding.util.ParserHelper; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.android.databinding.writer.FileWriter; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; /** * The main class that handles parsing files and generating classes. */ public class DataBinder { private static final String XPATH_VARIABLE_DEFINITIONS = "//variable"; private static final String XPATH_BINDING_2_EXPR = "//@*[starts-with(., '@{') and substring(., string-length(.)) = '}']"; private static final String XPATH_BINDING_ELEMENTS = XPATH_BINDING_2_EXPR + "/.."; private static final String XPATH_IMPORT_DEFINITIONS = "//import"; final String LAYOUT_PREFIX = "@layout/"; HashMap<String, List<LayoutBinder>> mLayoutBinders = new HashMap<>(); public LayoutBinder parseXml(File xml, String pkg) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException { L.d("parsing file %s", xml.getAbsolutePath()); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); final Document doc = builder.parse(xml); final XPathFactory xPathFactory = XPathFactory.newInstance(); final XPath xPath = xPathFactory.newXPath(); // final LayoutBinder layoutBinder = new LayoutBinder(doc); List<Node> variableNodes = getVariableNodes(doc, xPath); L.d("number of variable nodes %d", variableNodes.size()); for (Node item : variableNodes) { L.d("reading variable node %s", item); NamedNodeMap attributes = item.getAttributes(); String variableName = attributes.getNamedItem("name").getNodeValue(); String variableType = attributes.getNamedItem("type").getNodeValue(); L.d("name: %s, type:%s", variableName, variableType); layoutBinder.addVariable(variableName, variableType); } final List<Node> imports = getImportNodes(doc, xPath); L.d("import node count %d", imports.size()); for (Node item : imports) { NamedNodeMap attributes = item.getAttributes(); String type = attributes.getNamedItem("type").getNodeValue(); final Node aliasNode = attributes.getNamedItem("alias"); final String alias; if (aliasNode == null) { final String[] split = StringUtils.split(type, '.'); alias = split[split.length - 1]; } else { alias = aliasNode.getNodeValue(); } layoutBinder.addImport(alias, type); } List<LayoutBinder> mLayoutBinders = new ArrayList<>(); final List<Node> bindingNodes = getBindingNodes(doc, xPath); L.d("number of binding nodes %d", bindingNodes.size()); for (Node parent : bindingNodes) { NamedNodeMap attributes = parent.getAttributes(); Node id = attributes.getNamedItem("android:id"); if (id != null) { String nodeName = parent.getNodeName(); String layoutName = null; final String fullClassName; if ("include".equals(nodeName)) { // get the layout attribute final Node includedLayout = attributes.getNamedItem("layout"); Preconditions.checkNotNull(includedLayout, "must include a layout"); final String includeValue = includedLayout.getNodeValue(); Preconditions.checkArgument(includeValue.startsWith(LAYOUT_PREFIX)); // if user is binding something there, there MUST be a layout file to be // generated. layoutName = includeValue.substring(LAYOUT_PREFIX.length()); L.d("replaced node name to " + nodeName); fullClassName = pkg + "." + ParserHelper.INSTANCE$.toClassName(layoutName) + "Binder"; } else { fullClassName = getFullViewClassName(nodeName); } final BindingTarget bindingTarget = layoutBinder .createBindingTarget(id.getNodeValue(), fullClassName, true); bindingTarget.setIncludedLayout(layoutName); int attrCount = attributes.getLength(); for (int i = 0; i < attrCount; i ++) { final Node attr = attributes.item(i); String value = attr.getNodeValue(); if (value.charAt(0) == '@' && value.charAt(1) == '{' && value.charAt(value.length() - 1) == '}') { final String strippedValue = value.substring(2, value.length() - 1); bindingTarget.addBinding(attr.getNodeName(), layoutBinder.parse(strippedValue)); } } } else { throw new RuntimeException("data binding requires id for now."); } } private FileWriter mFileWriter; if (!layoutBinder.isEmpty()) { if (!mLayoutBinders.containsKey(xml.getName())) { mLayoutBinders.put(xml.getName(), new ArrayList<LayoutBinder>()); public DataBinder(ResourceBundle resourceBundle) { L.d("reading resource bundle into data binder"); for (Map.Entry<String, List<ResourceBundle.LayoutFileBundle>> entry : resourceBundle.getLayoutBundles().entrySet()) { for (ResourceBundle.LayoutFileBundle bundle : entry.getValue()) { mLayoutBinders.add(new LayoutBinder(resourceBundle, bundle)); } mLayoutBinders.get(xml.getName()).add(layoutBinder); } return layoutBinder; } private List<Node> getBindingNodes(Document doc, XPath xPath) throws XPathExpressionException { return get(doc, xPath, XPATH_BINDING_ELEMENTS); public List<LayoutBinder> getLayoutBinders() { return mLayoutBinders; } private List<Node> getVariableNodes(Document doc, XPath xPath) throws XPathExpressionException { return get(doc, xPath, XPATH_VARIABLE_DEFINITIONS); public void writerBinderInterfaces(File outputDir) { outputDir.mkdirs(); Set<String> writtenFiles = new HashSet<>(); for (LayoutBinder layoutBinder : mLayoutBinders) { String interfaceName = layoutBinder.getInterfaceName(); if (writtenFiles.contains(interfaceName)) { continue; } private List<Node> getImportNodes(Document doc, XPath xPath) throws XPathExpressionException { return get(doc, xPath, XPATH_IMPORT_DEFINITIONS); mFileWriter.writeToFile(new File(outputDir, interfaceName + ".java"), layoutBinder.writeViewBinderInterface()); } private List<Node> get(Document doc, XPath xPath, String pattern) throws XPathExpressionException { final XPathExpression expr = xPath.compile(pattern); return toList((NodeList) expr.evaluate(doc, XPathConstants.NODESET)); } private List<Node> toList(NodeList nodeList) { List<Node> result = new ArrayList<>(); for (int i = 0; i < nodeList.getLength(); i ++) { result.add(nodeList.item(i)); public void writeBinders(File outputDir) { L.d("writing binders to %s", outputDir.getAbsoluteFile()); for (LayoutBinder layoutBinder : mLayoutBinders) { L.d("binder: %s %s %s", layoutBinder.getId(), layoutBinder.getClassName(), layoutBinder.getInterfaceName()); mFileWriter.writeToFile(new File(outputDir, layoutBinder.getClassName() + ".java"), layoutBinder.writeViewBinder()); } return result; } private String getFullViewClassName(String viewName) { if (viewName.indexOf('.') == -1) { if (Objects.equals(viewName, "View") || Objects.equals(viewName, "ViewGroup") || Objects.equals(viewName, "ViewStub")) { return "android.view." + viewName; } return "android.widget." + viewName; } return viewName; public void setFileWriter(FileWriter fileWriter) { mFileWriter = fileWriter; } public HashMap<String, List<LayoutBinder>> getLayoutBinders() { return mLayoutBinders; public FileWriter getFileWriter() { return mFileWriter; } } Loading
tools/data-binding/TestApp/src/main/java/com/android/databinding/testapp/TestActivity.java +7 −0 Original line number Diff line number Diff line Loading @@ -14,7 +14,14 @@ package com.android.databinding.testapp; import android.app.Activity; import android.os.Bundle; import android.view.WindowManager; public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } }
tools/data-binding/compiler/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ buildscript { dependencies { compile 'junit:junit:4.12' compile 'org.apache.commons:commons-lang3:3.3.2' compile 'org.apache.commons:commons-io:1.3.2' compile 'com.google.guava:guava:18.0' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" compile project(":baseLibrary") Loading
tools/data-binding/compiler/src/main/java/com/android/databinding/BindingTarget.java +12 −27 Original line number Diff line number Diff line Loading @@ -20,71 +20,56 @@ import com.android.databinding.expr.Expr; import com.android.databinding.expr.ExprModel; import com.android.databinding.reflection.ReflectionAnalyzer; import com.android.databinding.reflection.ReflectionClass; import com.android.databinding.store.ResourceBundle; import java.util.ArrayList; import java.util.List; public class BindingTarget { String mId; String mViewClass; List<Binding> mBindings = new ArrayList<>(); ExprModel mModel; ReflectionClass mResolvedClass; String mIncludedLayout; // if this target presents itself in multiple layout files with different view types, // it receives an interface type and should use it in the getter instead. String mInterfaceType; // if this target is inherited from a common layout interface, used is false so that we don't // create find view by id etc for it. boolean mUsed; public BindingTarget(String id, String viewClass, boolean used) { mId = id; mViewClass = viewClass; mUsed = used; private ResourceBundle.BindingTargetBundle mBundle; public BindingTarget(ResourceBundle.BindingTargetBundle bundle) { mBundle = bundle; } public boolean isUsed() { return mUsed; return mBundle.isUsed(); } public void addBinding(String name, Expr expr) { mBindings.add(new Binding(this, name, expr)); } public void setInterfaceType(String interfaceType) { mInterfaceType = interfaceType; } public String getInterfaceType() { return mInterfaceType == null ? mViewClass : mInterfaceType; return mBundle.getInterfaceType() == null ? mBundle.getFullClassName() : mBundle.getInterfaceType(); } public String getId() { return mId; return mBundle.getId(); } public String getViewClass() { return mViewClass; return mBundle.getFullClassName(); } public ReflectionClass getResolvedType() { if (mResolvedClass == null) { mResolvedClass = ReflectionAnalyzer.getInstance().findClass(mViewClass); mResolvedClass = ReflectionAnalyzer.getInstance().findClass(mBundle.getFullClassName()); } return mResolvedClass; } public String getIncludedLayout() { return mIncludedLayout; return mBundle.getIncludedLayout(); } public boolean isBinder() { return mIncludedLayout != null; } public void setIncludedLayout(String includedLayout) { mIncludedLayout = includedLayout; return getIncludedLayout() != null; } public List<Binding> getBindings() { Loading
tools/data-binding/compiler/src/main/java/com/android/databinding/CompilerChef.java 0 → 100644 +153 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * 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.android.databinding; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.android.databinding.store.LayoutFileParser; import com.android.databinding.store.ResourceBundle; import com.android.databinding.util.L; import com.android.databinding.writer.DataBinderWriter; import com.android.databinding.writer.FileWriter; import com.android.databinding.writer.FileWriterImpl; import org.xml.sax.SAXException; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpressionException; /** * Chef class for compiler. * * Different build systems can initiate a version of this to handle their work */ public class CompilerChef { private FileWriter mFileWriter = new FileWriterImpl(); private LayoutFileParser mLayoutFileParser; private ResourceBundle mResourceBundle; private DataBinder mDataBinder; private File mOutputBaseDir; private String mAppPackage; private List<File> mResourceFolders; public void setupForParsing(String appPkg, List<File> resourceFolders, File codegenTargetFolder) { mOutputBaseDir = codegenTargetFolder; mResourceFolders = resourceFolders; mAppPackage = appPkg; } public void processResources() throws ParserConfigurationException, SAXException, XPathExpressionException, IOException { if (mResourceBundle != null) { return; // already processed } mResourceBundle = new ResourceBundle(); mResourceBundle.setAppPackage(mAppPackage); mLayoutFileParser = new LayoutFileParser(); int layoutId = 0; for (File resFolder : Iterables.filter(mResourceFolders, fileExists)) { for (File layoutFolder : resFolder.listFiles(layoutFolderFilter)) { for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) { final ResourceBundle.LayoutFileBundle bundle = mLayoutFileParser .parseXml(xmlFile, mAppPackage, layoutId); if (bundle != null && !bundle.isEmpty()) { mResourceBundle.addLayoutBundle(bundle, layoutId); layoutId ++; } } } } mResourceBundle.validateMultiResLayouts(); } public void ensureDataBinder() { if (mDataBinder == null) { mDataBinder = new DataBinder(mResourceBundle); mDataBinder.setFileWriter(mFileWriter); } } public boolean hasAnythingToGenerate() { L.d("checking if we have anything to genreate. bundle size: %s", mResourceBundle == null ? -1 : mResourceBundle.getLayoutBundles().size()); return mResourceBundle != null && mResourceBundle.getLayoutBundles().size() > 0; } public void writeDbrFile(File folder) { ensureDataBinder(); final String pkg = "com.android.databinding.library"; DataBinderWriter dbr = new DataBinderWriter(pkg, mResourceBundle.getAppPackage(), "GeneratedDataBinderRenderer", mDataBinder.getLayoutBinders()); if (folder == null) { folder = new File(mOutputBaseDir.getAbsolutePath() + "/" + pkg.replace('.','/')); } folder.mkdirs(); if (dbr.getLayoutBinders().size() > 0) { mFileWriter.writeToFile(new File(folder, dbr.getClassName() + ".java"), dbr.write()); } } public void setOutputBaseDir(File baseDir) { mOutputBaseDir = baseDir; } public void writeViewBinderInterfaces(File folder) { ensureDataBinder(); if (folder == null) { folder = new File(mOutputBaseDir.getAbsolutePath() + "/" + mResourceBundle.getAppPackage().replace( '.', '/') + "/generated"); } mDataBinder.writerBinderInterfaces(folder); } public void writeViewBinders(File folder) { ensureDataBinder(); if (folder == null) { folder = new File(mOutputBaseDir.getAbsolutePath() + "/" + mResourceBundle.getAppPackage().replace( '.', '/') + "/generated"); } mDataBinder.writeBinders(folder); } private final Predicate<File> fileExists = new Predicate<File>() { @Override public boolean apply(File input) { return input.exists() && input.canRead(); } }; private final FilenameFilter layoutFolderFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith("layout"); } }; private final FilenameFilter xmlFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".xml"); } }; }
tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java +37 −148 Original line number Diff line number Diff line Loading @@ -15,176 +15,65 @@ */ package com.android.databinding; import com.google.common.base.Preconditions; import com.android.databinding.store.ResourceBundle; import com.android.databinding.util.L; import com.android.databinding.util.ParserHelper; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.android.databinding.writer.FileWriter; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; /** * The main class that handles parsing files and generating classes. */ public class DataBinder { private static final String XPATH_VARIABLE_DEFINITIONS = "//variable"; private static final String XPATH_BINDING_2_EXPR = "//@*[starts-with(., '@{') and substring(., string-length(.)) = '}']"; private static final String XPATH_BINDING_ELEMENTS = XPATH_BINDING_2_EXPR + "/.."; private static final String XPATH_IMPORT_DEFINITIONS = "//import"; final String LAYOUT_PREFIX = "@layout/"; HashMap<String, List<LayoutBinder>> mLayoutBinders = new HashMap<>(); public LayoutBinder parseXml(File xml, String pkg) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException { L.d("parsing file %s", xml.getAbsolutePath()); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); final Document doc = builder.parse(xml); final XPathFactory xPathFactory = XPathFactory.newInstance(); final XPath xPath = xPathFactory.newXPath(); // final LayoutBinder layoutBinder = new LayoutBinder(doc); List<Node> variableNodes = getVariableNodes(doc, xPath); L.d("number of variable nodes %d", variableNodes.size()); for (Node item : variableNodes) { L.d("reading variable node %s", item); NamedNodeMap attributes = item.getAttributes(); String variableName = attributes.getNamedItem("name").getNodeValue(); String variableType = attributes.getNamedItem("type").getNodeValue(); L.d("name: %s, type:%s", variableName, variableType); layoutBinder.addVariable(variableName, variableType); } final List<Node> imports = getImportNodes(doc, xPath); L.d("import node count %d", imports.size()); for (Node item : imports) { NamedNodeMap attributes = item.getAttributes(); String type = attributes.getNamedItem("type").getNodeValue(); final Node aliasNode = attributes.getNamedItem("alias"); final String alias; if (aliasNode == null) { final String[] split = StringUtils.split(type, '.'); alias = split[split.length - 1]; } else { alias = aliasNode.getNodeValue(); } layoutBinder.addImport(alias, type); } List<LayoutBinder> mLayoutBinders = new ArrayList<>(); final List<Node> bindingNodes = getBindingNodes(doc, xPath); L.d("number of binding nodes %d", bindingNodes.size()); for (Node parent : bindingNodes) { NamedNodeMap attributes = parent.getAttributes(); Node id = attributes.getNamedItem("android:id"); if (id != null) { String nodeName = parent.getNodeName(); String layoutName = null; final String fullClassName; if ("include".equals(nodeName)) { // get the layout attribute final Node includedLayout = attributes.getNamedItem("layout"); Preconditions.checkNotNull(includedLayout, "must include a layout"); final String includeValue = includedLayout.getNodeValue(); Preconditions.checkArgument(includeValue.startsWith(LAYOUT_PREFIX)); // if user is binding something there, there MUST be a layout file to be // generated. layoutName = includeValue.substring(LAYOUT_PREFIX.length()); L.d("replaced node name to " + nodeName); fullClassName = pkg + "." + ParserHelper.INSTANCE$.toClassName(layoutName) + "Binder"; } else { fullClassName = getFullViewClassName(nodeName); } final BindingTarget bindingTarget = layoutBinder .createBindingTarget(id.getNodeValue(), fullClassName, true); bindingTarget.setIncludedLayout(layoutName); int attrCount = attributes.getLength(); for (int i = 0; i < attrCount; i ++) { final Node attr = attributes.item(i); String value = attr.getNodeValue(); if (value.charAt(0) == '@' && value.charAt(1) == '{' && value.charAt(value.length() - 1) == '}') { final String strippedValue = value.substring(2, value.length() - 1); bindingTarget.addBinding(attr.getNodeName(), layoutBinder.parse(strippedValue)); } } } else { throw new RuntimeException("data binding requires id for now."); } } private FileWriter mFileWriter; if (!layoutBinder.isEmpty()) { if (!mLayoutBinders.containsKey(xml.getName())) { mLayoutBinders.put(xml.getName(), new ArrayList<LayoutBinder>()); public DataBinder(ResourceBundle resourceBundle) { L.d("reading resource bundle into data binder"); for (Map.Entry<String, List<ResourceBundle.LayoutFileBundle>> entry : resourceBundle.getLayoutBundles().entrySet()) { for (ResourceBundle.LayoutFileBundle bundle : entry.getValue()) { mLayoutBinders.add(new LayoutBinder(resourceBundle, bundle)); } mLayoutBinders.get(xml.getName()).add(layoutBinder); } return layoutBinder; } private List<Node> getBindingNodes(Document doc, XPath xPath) throws XPathExpressionException { return get(doc, xPath, XPATH_BINDING_ELEMENTS); public List<LayoutBinder> getLayoutBinders() { return mLayoutBinders; } private List<Node> getVariableNodes(Document doc, XPath xPath) throws XPathExpressionException { return get(doc, xPath, XPATH_VARIABLE_DEFINITIONS); public void writerBinderInterfaces(File outputDir) { outputDir.mkdirs(); Set<String> writtenFiles = new HashSet<>(); for (LayoutBinder layoutBinder : mLayoutBinders) { String interfaceName = layoutBinder.getInterfaceName(); if (writtenFiles.contains(interfaceName)) { continue; } private List<Node> getImportNodes(Document doc, XPath xPath) throws XPathExpressionException { return get(doc, xPath, XPATH_IMPORT_DEFINITIONS); mFileWriter.writeToFile(new File(outputDir, interfaceName + ".java"), layoutBinder.writeViewBinderInterface()); } private List<Node> get(Document doc, XPath xPath, String pattern) throws XPathExpressionException { final XPathExpression expr = xPath.compile(pattern); return toList((NodeList) expr.evaluate(doc, XPathConstants.NODESET)); } private List<Node> toList(NodeList nodeList) { List<Node> result = new ArrayList<>(); for (int i = 0; i < nodeList.getLength(); i ++) { result.add(nodeList.item(i)); public void writeBinders(File outputDir) { L.d("writing binders to %s", outputDir.getAbsoluteFile()); for (LayoutBinder layoutBinder : mLayoutBinders) { L.d("binder: %s %s %s", layoutBinder.getId(), layoutBinder.getClassName(), layoutBinder.getInterfaceName()); mFileWriter.writeToFile(new File(outputDir, layoutBinder.getClassName() + ".java"), layoutBinder.writeViewBinder()); } return result; } private String getFullViewClassName(String viewName) { if (viewName.indexOf('.') == -1) { if (Objects.equals(viewName, "View") || Objects.equals(viewName, "ViewGroup") || Objects.equals(viewName, "ViewStub")) { return "android.view." + viewName; } return "android.widget." + viewName; } return viewName; public void setFileWriter(FileWriter fileWriter) { mFileWriter = fileWriter; } public HashMap<String, List<LayoutBinder>> getLayoutBinders() { return mLayoutBinders; public FileWriter getFileWriter() { return mFileWriter; } }