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