ProcessExpressions.java revision 1907fd71019ef16d4fc2953d56f1ec0702275aa0
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; 22import org.apache.commons.lang3.SystemUtils; 23 24import android.databinding.BindingBuildInfo; 25import android.databinding.tool.CompilerChef; 26import android.databinding.tool.LayoutXmlProcessor; 27import android.databinding.tool.reflection.SdkUtil; 28import android.databinding.tool.store.ResourceBundle; 29import android.databinding.tool.util.GenerationalClassUtil; 30import android.databinding.tool.util.L; 31import android.databinding.tool.util.Preconditions; 32 33import java.io.File; 34import java.io.FilenameFilter; 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.Serializable; 38import java.util.ArrayList; 39import java.util.HashMap; 40import java.util.HashSet; 41import java.util.List; 42import java.util.Map; 43import java.util.Set; 44 45import javax.annotation.processing.ProcessingEnvironment; 46import javax.annotation.processing.RoundEnvironment; 47import javax.xml.bind.JAXBContext; 48import javax.xml.bind.JAXBException; 49import javax.xml.bind.Unmarshaller; 50 51public class ProcessExpressions extends ProcessDataBinding.ProcessingStep { 52 public ProcessExpressions() { 53 } 54 55 @Override 56 public boolean onHandleStep(RoundEnvironment roundEnvironment, 57 ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) 58 throws JAXBException { 59 ResourceBundle resourceBundle; 60 SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot())); 61 resourceBundle = new ResourceBundle(buildInfo.modulePackage()); 62 List<IntermediateV2> intermediateList = loadDependencyIntermediates(); 63 for (Intermediate intermediate : intermediateList) { 64 try { 65 intermediate.appendTo(resourceBundle); 66 } catch (Throwable throwable) { 67 L.e(throwable, "unable to prepare resource bundle"); 68 } 69 } 70 71 IntermediateV2 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir(), 72 intermediateList); 73 if (mine != null) { 74 mine.updateOverridden(resourceBundle); 75 intermediateList.add(mine); 76 saveIntermediate(processingEnvironment, buildInfo, mine); 77 mine.appendTo(resourceBundle); 78 } 79 // generate them here so that bindable parser can read 80 try { 81 writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk(), 82 buildInfo.exportClassListTo()); 83 } catch (Throwable t) { 84 L.e(t, "cannot generate view binders"); 85 } 86 return true; 87 } 88 89 private List<IntermediateV2> loadDependencyIntermediates() { 90 final List<Intermediate> original = GenerationalClassUtil.loadObjects( 91 GenerationalClassUtil.ExtensionFilter.LAYOUT); 92 final List<IntermediateV2> upgraded = new ArrayList<IntermediateV2>(original.size()); 93 for (Intermediate intermediate : original) { 94 final Intermediate updatedIntermediate = intermediate.upgrade(); 95 Preconditions.check(updatedIntermediate instanceof IntermediateV2, "Incompatible data" 96 + " binding dependency. Please update your dependencies or recompile them with" 97 + " application module's data binding version."); 98 //noinspection ConstantConditions 99 upgraded.add((IntermediateV2) updatedIntermediate); 100 } 101 return upgraded; 102 } 103 104 private void saveIntermediate(ProcessingEnvironment processingEnvironment, 105 BindingBuildInfo buildInfo, IntermediateV2 intermediate) { 106 GenerationalClassUtil.writeIntermediateFile(processingEnvironment, 107 buildInfo.modulePackage(), buildInfo.modulePackage() + 108 GenerationalClassUtil.ExtensionFilter.LAYOUT.getExtension(), 109 intermediate); 110 } 111 112 @Override 113 public void onProcessingOver(RoundEnvironment roundEnvironment, 114 ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) { 115 } 116 117 private IntermediateV2 createIntermediateFromLayouts(String layoutInfoFolderPath, 118 List<IntermediateV2> intermediateList) { 119 final Set<String> excludeList = new HashSet<String>(); 120 for (IntermediateV2 lib : intermediateList) { 121 excludeList.addAll(lib.mLayoutInfoMap.keySet()); 122 } 123 final File layoutInfoFolder = new File(layoutInfoFolderPath); 124 if (!layoutInfoFolder.isDirectory()) { 125 L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath); 126 return null; 127 } 128 IntermediateV2 result = new IntermediateV2(); 129 for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() { 130 @Override 131 public boolean accept(File dir, String name) { 132 return name.endsWith(".xml") && !excludeList.contains(name); 133 } 134 })) { 135 try { 136 result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile)); 137 } catch (IOException e) { 138 L.e(e, "cannot load layout file information. Try a clean build"); 139 } 140 } 141 return result; 142 } 143 144 private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule, 145 final int minSdk, String exportClassNamesTo) 146 throws JAXBException { 147 final CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter()); 148 compilerChef.sealModels(); 149 compilerChef.writeComponent(); 150 if (compilerChef.hasAnythingToGenerate()) { 151 compilerChef.writeViewBinderInterfaces(forLibraryModule); 152 if (!forLibraryModule) { 153 compilerChef.writeViewBinders(minSdk); 154 } 155 } 156 if (forLibraryModule && exportClassNamesTo == null) { 157 L.e("When compiling a library module, build info must include exportClassListTo path"); 158 } 159 if (forLibraryModule) { 160 Set<String> classNames = compilerChef.getWrittenClassNames(); 161 String out = StringUtils.join(classNames, SystemUtils.LINE_SEPARATOR); 162 L.d("Writing list of classes to %s . \nList:%s", exportClassNamesTo, out); 163 try { 164 //noinspection ConstantConditions 165 FileUtils.write(new File(exportClassNamesTo), out); 166 } catch (IOException e) { 167 L.e(e, "Cannot create list of written classes"); 168 } 169 } 170 mCallback.onChefReady(compilerChef, forLibraryModule, minSdk); 171 } 172 173 public interface Intermediate extends Serializable { 174 175 Intermediate upgrade(); 176 177 void appendTo(ResourceBundle resourceBundle) throws Throwable; 178 } 179 180 public static class IntermediateV1 implements Intermediate { 181 182 transient Unmarshaller mUnmarshaller; 183 184 // name to xml content map 185 Map<String, String> mLayoutInfoMap = new HashMap<String, String>(); 186 187 @Override 188 public Intermediate upgrade() { 189 final IntermediateV2 updated = new IntermediateV2(); 190 updated.mLayoutInfoMap = mLayoutInfoMap; 191 updated.mUnmarshaller = mUnmarshaller; 192 return updated; 193 } 194 195 @Override 196 public void appendTo(ResourceBundle resourceBundle) throws JAXBException { 197 if (mUnmarshaller == null) { 198 JAXBContext context = JAXBContext 199 .newInstance(ResourceBundle.LayoutFileBundle.class); 200 mUnmarshaller = context.createUnmarshaller(); 201 } 202 for (String content : mLayoutInfoMap.values()) { 203 final InputStream is = IOUtils.toInputStream(content); 204 try { 205 final ResourceBundle.LayoutFileBundle bundle 206 = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is); 207 resourceBundle.addLayoutBundle(bundle); 208 L.d("loaded layout info file %s", bundle); 209 } finally { 210 IOUtils.closeQuietly(is); 211 } 212 } 213 } 214 215 public void addEntry(String name, String contents) { 216 mLayoutInfoMap.put(name, contents); 217 } 218 219 // keeping the method to match deserialized structure 220 @SuppressWarnings("unused") 221 public void removeOverridden(List<Intermediate> existing) { 222 } 223 } 224 225 public static class IntermediateV2 extends IntermediateV1 { 226 // specify so that we can define updates ourselves. 227 private static final long serialVersionUID = 2L; 228 @Override 229 public void appendTo(ResourceBundle resourceBundle) throws JAXBException { 230 for (Map.Entry<String, String> entry : mLayoutInfoMap.entrySet()) { 231 final InputStream is = IOUtils.toInputStream(entry.getValue()); 232 try { 233 final ResourceBundle.LayoutFileBundle bundle = ResourceBundle.LayoutFileBundle 234 .fromXML(is); 235 resourceBundle.addLayoutBundle(bundle); 236 L.d("loaded layout info file %s", bundle); 237 } finally { 238 IOUtils.closeQuietly(is); 239 } 240 } 241 } 242 243 /** 244 * if a layout is overridden from a module (which happens when layout is auto-generated), 245 * we need to update its contents from the class that overrides it. 246 * This must be done before this bundle is saved, otherwise, it will not be recognized 247 * when it is used in another project. 248 */ 249 public void updateOverridden(ResourceBundle bundle) throws JAXBException { 250 // When a layout is copied from inherited module, it is eleminated while reading 251 // info files. (createIntermediateFromLayouts). 252 // Build process may also duplicate some files at compile time. This is where 253 // we detect those copies and force inherit their module and classname information. 254 final HashMap<String, List<ResourceBundle.LayoutFileBundle>> bundles = bundle 255 .getLayoutBundles(); 256 for (Map.Entry<String, String> info : mLayoutInfoMap.entrySet()) { 257 String key = LayoutXmlProcessor.exportLayoutNameFromInfoFileName(info.getKey()); 258 final List<ResourceBundle.LayoutFileBundle> existingList = bundles.get(key); 259 if (existingList != null && !existingList.isEmpty()) { 260 ResourceBundle.LayoutFileBundle myBundle = ResourceBundle.LayoutFileBundle 261 .fromXML(IOUtils.toInputStream(info.getValue())); 262 final ResourceBundle.LayoutFileBundle inheritFrom = existingList.get(0); 263 myBundle.inheritConfigurationFrom(inheritFrom); 264 L.d("inheriting data for %s (%s) from %s", info.getKey(), key, inheritFrom); 265 mLayoutInfoMap.put(info.getKey(), myBundle.toXML()); 266 } 267 } 268 } 269 } 270} 271