ProcessExpressions.java revision e4cd38824a6627b9fef229c549c636e35ad63b5f
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.databinding.annotationprocessor; 18 19import org.apache.commons.io.FileUtils; 20import org.apache.commons.io.IOUtils; 21import org.apache.commons.lang3.StringUtils; 22 23import android.databinding.BindingBuildInfo; 24import android.databinding.tool.CompilerChef; 25import android.databinding.tool.reflection.SdkUtil; 26import android.databinding.tool.store.ResourceBundle; 27import android.databinding.tool.util.GenerationalClassUtil; 28import android.databinding.tool.util.L; 29 30import java.io.File; 31import java.io.FilenameFilter; 32import java.io.IOException; 33import java.io.InputStream; 34import java.io.Serializable; 35import java.util.HashMap; 36import java.util.List; 37import java.util.Map; 38import java.util.Set; 39 40import javax.annotation.processing.ProcessingEnvironment; 41import javax.annotation.processing.RoundEnvironment; 42import javax.xml.bind.JAXBContext; 43import javax.xml.bind.JAXBException; 44import javax.xml.bind.Unmarshaller; 45 46public class ProcessExpressions extends ProcessDataBinding.ProcessingStep { 47 public ProcessExpressions() { 48 } 49 50 @Override 51 public boolean onHandleStep(RoundEnvironment roundEnvironment, 52 ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) { 53 ResourceBundle resourceBundle; 54 SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot())); 55 resourceBundle = new ResourceBundle(buildInfo.modulePackage()); 56 List<Intermediate> intermediateList = 57 GenerationalClassUtil.loadObjects( 58 GenerationalClassUtil.ExtensionFilter.LAYOUT); 59 IntermediateV1 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir()); 60 if (mine != null) { 61 mine.removeOverridden(intermediateList); 62 intermediateList.add(mine); 63 saveIntermediate(processingEnvironment, buildInfo, mine); 64 } 65 // generate them here so that bindable parser can read 66 try { 67 generateBinders(resourceBundle, buildInfo, intermediateList); 68 } catch (Throwable t) { 69 L.e(t, "cannot generate view binders"); 70 } 71 return true; 72 } 73 74 private void saveIntermediate(ProcessingEnvironment processingEnvironment, 75 BindingBuildInfo buildInfo, IntermediateV1 intermediate) { 76 GenerationalClassUtil.writeIntermediateFile(processingEnvironment, 77 buildInfo.modulePackage(), buildInfo.modulePackage() + 78 GenerationalClassUtil.ExtensionFilter.LAYOUT.getExtension(), 79 intermediate); 80 } 81 82 @Override 83 public void onProcessingOver(RoundEnvironment roundEnvironment, 84 ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) { 85 } 86 87 private void generateBinders(ResourceBundle resourceBundle, BindingBuildInfo buildInfo, 88 List<Intermediate> intermediates) 89 throws Throwable { 90 for (Intermediate intermediate : intermediates) { 91 intermediate.appendTo(resourceBundle); 92 } 93 writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk(), 94 buildInfo.exportClassListTo()); 95 } 96 97 private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) { 98 final File layoutInfoFolder = new File(layoutInfoFolderPath); 99 if (!layoutInfoFolder.isDirectory()) { 100 L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath); 101 return null; 102 } 103 IntermediateV1 result = new IntermediateV1(); 104 for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() { 105 @Override 106 public boolean accept(File dir, String name) { 107 return name.endsWith(".xml"); 108 } 109 })) { 110 try { 111 result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile)); 112 } catch (IOException e) { 113 L.e(e, "cannot load layout file information. Try a clean build"); 114 } 115 } 116 return result; 117 } 118 119 private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule, 120 final int minSdk, String exportClassNamesTo) 121 throws JAXBException { 122 final CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter()); 123 if (compilerChef.hasAnythingToGenerate()) { 124 compilerChef.sealModels(); 125 compilerChef.writeComponent(); 126 compilerChef.writeViewBinderInterfaces(forLibraryModule); 127 if (!forLibraryModule) { 128 compilerChef.writeViewBinders(minSdk); 129 } 130 } 131 if (forLibraryModule && exportClassNamesTo == null) { 132 L.e("When compiling a library module, build info must include exportClassListTo path"); 133 } 134 if (forLibraryModule) { 135 Set<String> classNames = compilerChef.getWrittenClassNames(); 136 String out = StringUtils.join(classNames, System.getProperty("line.separator")); 137 L.d("Writing list of classes to %s . \nList:%s", exportClassNamesTo, out); 138 try { 139 //noinspection ConstantConditions 140 FileUtils.write(new File(exportClassNamesTo), out); 141 } catch (IOException e) { 142 L.e(e, "Cannot create list of written classes"); 143 } 144 } 145 mCallback.onChefReady(compilerChef, forLibraryModule, minSdk); 146 } 147 148 public static interface Intermediate extends Serializable { 149 150 Intermediate upgrade(); 151 152 public void appendTo(ResourceBundle resourceBundle) throws Throwable; 153 } 154 155 public static class IntermediateV1 implements Intermediate { 156 157 transient Unmarshaller mUnmarshaller; 158 159 // name to xml content map 160 Map<String, String> mLayoutInfoMap = new HashMap<String, String>(); 161 162 @Override 163 public Intermediate upgrade() { 164 return this; 165 } 166 167 @Override 168 public void appendTo(ResourceBundle resourceBundle) throws JAXBException { 169 if (mUnmarshaller == null) { 170 JAXBContext context = JAXBContext 171 .newInstance(ResourceBundle.LayoutFileBundle.class); 172 mUnmarshaller = context.createUnmarshaller(); 173 } 174 for (String content : mLayoutInfoMap.values()) { 175 final InputStream is = IOUtils.toInputStream(content); 176 try { 177 final ResourceBundle.LayoutFileBundle bundle 178 = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is); 179 resourceBundle.addLayoutBundle(bundle); 180 L.d("loaded layout info file %s", bundle); 181 } finally { 182 IOUtils.closeQuietly(is); 183 } 184 } 185 } 186 187 public void addEntry(String name, String contents) { 188 mLayoutInfoMap.put(name, contents); 189 } 190 191 public void removeOverridden(List<Intermediate> existing) { 192 // this is the way we get rid of files that are copied from previous modules 193 // it is important to do this before saving the intermediate file 194 for (Intermediate old : existing) { 195 if (old instanceof IntermediateV1) { 196 IntermediateV1 other = (IntermediateV1) old; 197 for (String key : other.mLayoutInfoMap.keySet()) { 198 // TODO we should consider the original file as the key here 199 // but aapt probably cannot provide that information 200 if (mLayoutInfoMap.remove(key) != null) { 201 L.d("removing %s from bundle because it came from another module", key); 202 } 203 } 204 } 205 } 206 } 207 } 208} 209