Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e5741e49 authored by Yigit Boyar's avatar Yigit Boyar
Browse files

Separate resource parser

Change-Id: I3b5f44b48269130834d013425dbaf79fe084c855
parent fbdb3c08
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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")
+12 −27
Original line number Diff line number Diff line
@@ -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() {
+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");
        }
    };
}
+37 −148
Original line number Diff line number Diff line
@@ -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