/*
* 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 android.databinding.annotationprocessor;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import android.databinding.BindingBuildInfo;
import android.databinding.tool.CompilerChef;
import android.databinding.tool.reflection.SdkUtil;
import android.databinding.tool.store.ResourceBundle;
import android.databinding.tool.util.GenerationalClassUtil;
import android.databinding.tool.util.L;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
public ProcessExpressions() {
}
@Override
public boolean onHandleStep(RoundEnvironment roundEnvironment,
ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
ResourceBundle resourceBundle;
SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot()));
resourceBundle = new ResourceBundle(buildInfo.modulePackage());
List<Intermediate> intermediateList =
GenerationalClassUtil.loadObjects(
GenerationalClassUtil.ExtensionFilter.LAYOUT);
IntermediateV1 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir());
if (mine != null) {
mine.removeOverridden(intermediateList);
intermediateList.add(mine);
saveIntermediate(processingEnvironment, buildInfo, mine);
}
// generate them here so that bindable parser can read
try {
generateBinders(resourceBundle, buildInfo, intermediateList);
} catch (Throwable t) {
L.e(t, "cannot generate view binders");
}
return true;
}
private void saveIntermediate(ProcessingEnvironment processingEnvironment,
BindingBuildInfo buildInfo, IntermediateV1 intermediate) {
GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
buildInfo.modulePackage(), buildInfo.modulePackage() +
GenerationalClassUtil.ExtensionFilter.LAYOUT.getExtension(),
intermediate);
}
@Override
public void onProcessingOver(RoundEnvironment roundEnvironment,
ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
}
private void generateBinders(ResourceBundle resourceBundle, BindingBuildInfo buildInfo,
List<Intermediate> intermediates)
throws Throwable {
for (Intermediate intermediate : intermediates) {
intermediate.appendTo(resourceBundle);
}
writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk(),
buildInfo.exportClassListTo());
}
private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) {
final File layoutInfoFolder = new File(layoutInfoFolderPath);
if (!layoutInfoFolder.isDirectory()) {
L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath);
return null;
}
IntermediateV1 result = new IntermediateV1();
for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".xml");
}
})) {
try {
result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile));
} catch (IOException e) {
L.e(e, "cannot load layout file information. Try a clean build");
}
}
return result;
}
private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule,
final int minSdk, String exportClassNamesTo)
throws JAXBException {
final CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter());
compilerChef.sealModels();
compilerChef.writeComponent();
if (compilerChef.hasAnythingToGenerate()) {
compilerChef.writeViewBinderInterfaces(forLibraryModule);
if (!forLibraryModule) {
compilerChef.writeViewBinders(minSdk);
}
}
if (forLibraryModule && exportClassNamesTo == null) {
L.e("When compiling a library module, build info must include exportClassListTo path");
}
if (forLibraryModule) {
Set<String> classNames = compilerChef.getWrittenClassNames();
String out = StringUtils.join(classNames, System.getProperty("line.separator"));
L.d("Writing list of classes to %s . \nList:%s", exportClassNamesTo, out);
try {
//noinspection ConstantConditions
FileUtils.write(new File(exportClassNamesTo), out);
} catch (IOException e) {
L.e(e, "Cannot create list of written classes");
}
}
mCallback.onChefReady(compilerChef, forLibraryModule, minSdk);
}
public static interface Intermediate extends Serializable {
Intermediate upgrade();
public void appendTo(ResourceBundle resourceBundle) throws Throwable;
}
public static class IntermediateV1 implements Intermediate {
transient Unmarshaller mUnmarshaller;
// name to xml content map
Map<String, String> mLayoutInfoMap = new HashMap<String, String>();
@Override
public Intermediate upgrade() {
return this;
}
@Override
public void appendTo(ResourceBundle resourceBundle) throws JAXBException {
if (mUnmarshaller == null) {
JAXBContext context = JAXBContext
.newInstance(ResourceBundle.LayoutFileBundle.class);
mUnmarshaller = context.createUnmarshaller();
}
for (String content : mLayoutInfoMap.values()) {
final InputStream is = IOUtils.toInputStream(content);
try {
final ResourceBundle.LayoutFileBundle bundle
= (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is);
resourceBundle.addLayoutBundle(bundle);
L.d("loaded layout info file %s", bundle);
} finally {
IOUtils.closeQuietly(is);
}
}
}
public void addEntry(String name, String contents) {
mLayoutInfoMap.put(name, contents);
}
public void removeOverridden(List<Intermediate> existing) {
// this is the way we get rid of files that are copied from previous modules
// it is important to do this before saving the intermediate file
for (Intermediate old : existing) {
if (old instanceof IntermediateV1) {
IntermediateV1 other = (IntermediateV1) old;
for (String key : other.mLayoutInfoMap.keySet()) {
// TODO we should consider the original file as the key here
// but aapt probably cannot provide that information
if (mLayoutInfoMap.remove(key) != null) {
L.d("removing %s from bundle because it came from another module", key);
}
}
}
}
}
}
}