Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / compilation / DatastoreCallbacksConfigWriter.java
blob34922787e7e9f883dab216d964db26b320653f38
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 package com.google.appengine.tools.compilation;
4 import com.google.common.base.Joiner;
5 import com.google.common.base.Preconditions;
6 import com.google.common.base.Splitter;
7 import com.google.common.collect.LinkedHashMultimap;
8 import com.google.common.collect.Lists;
9 import com.google.common.collect.Maps;
10 import com.google.common.collect.Multimap;
11 import com.google.common.collect.Sets;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Properties;
21 import java.util.Set;
23 /**
24 * Helper that keeps track of the callbacks we encounter and writes them out in
25 * the appropriate format.
27 * @see DatastoreCallbacksProcessor
30 class DatastoreCallbacksConfigWriter {
32 static final String INCORRECT_FORMAT_MESSAGE = "Existing config file has incorrect "
33 + "format version. Please do a clean rebuild of your application.";
35 /**
36 * Property that stores the format version of the individual entries. This
37 * property is not a valid callback key, so there is no risk of a collision with
38 * real data.
40 static final String FORMAT_VERSION_PROPERTY = "DatastoreCallbacksFormatVersion";
42 /**
43 * Right now we only have one format version, so we can hardcode this.
45 private static final String FORMAT_VERSION = Integer.valueOf(1).toString();
47 /**
48 * Key is the kind (possibly the empty string), value is a {@link Map} where
49 * the key is the callback type and the value is a {@link List} of
50 * {@link String Strings}, each of which uniquely identifies a method that
51 * implements the callback for the kind and callback type. Each of these
52 * Strings is of the form fqn:method_name. We're not using a LinkedHashMap
53 * to make the iteration order deterministic because we're using
54 * {@link Properties} to write this data to a file, and Properties, unlike
55 * LinkedHashMap, does not have a deterministic ordering.
57 final Map<String, Multimap<String, String>> callbacks = Maps.newHashMap();
59 /**
60 * Maps the fully-qualified classnames of all classes with callbacks to the
61 * methods on those classes that are callbacks.
63 final Multimap<String, String> methodsWithCallbacks = LinkedHashMultimap.create();
65 final Properties props = new Properties();
67 /**
68 * Used to avoid logging about the same pruned class more than once. This
69 * member should not be read until after {@link #store(java.io.OutputStream)}
70 * has been called.
72 final Set<String> prunedClasses = Sets.newHashSet();
74 /**
75 * @param inputStream a (possibly {@code null} stream from which we can read
76 * an existing config
78 DatastoreCallbacksConfigWriter( InputStream inputStream) throws IOException {
79 if (inputStream != null) {
80 props.loadFromXML(inputStream);
81 Preconditions.checkState(
82 FORMAT_VERSION.equals(props.remove(FORMAT_VERSION_PROPERTY)), INCORRECT_FORMAT_MESSAGE);
86 /**
87 * Overwrites the contents of the provided {@link InputStream} (if provided)
88 * with the callback config and then stores it to disk.
90 public void store(OutputStream outputStream) throws IOException {
91 pruneExistingConfig();
93 props.setProperty(FORMAT_VERSION_PROPERTY, FORMAT_VERSION);
95 for (String kind : callbacks.keySet()) {
96 Multimap<String, String> kindMap = callbacks.get(kind);
97 for (String callbackType : kindMap.keySet()) {
98 String propKey = String.format("%s.%s", kind, callbackType);
99 Collection<String> newMethods = kindMap.get(callbackType);
100 StringBuilder combinedMethods = new StringBuilder();
101 String oldMethods = props.getProperty(propKey);
102 if (oldMethods != null) {
103 combinedMethods.append(oldMethods).append(",");
105 props.setProperty(propKey, Joiner.on(",").appendTo(combinedMethods, newMethods).toString());
108 props.storeToXML(outputStream, "Datastore Callbacks. DO NOT EDIT BY HAND!");
111 private void pruneExistingConfig() {
112 for (String propName : props.stringPropertyNames()) {
113 String propVal = props.getProperty(propName);
114 List<String> methodsToPreserve = Lists.newArrayList();
115 for (String method : Splitter.on(",").split(propVal)) {
116 String classStr = method.split(":")[0];
117 if (!methodsWithCallbacks.containsKey(classStr) && classExists(classStr)) {
118 methodsToPreserve.add(method);
121 if (methodsToPreserve.isEmpty()) {
122 props.remove(propName);
123 } else {
124 props.setProperty(propName, Joiner.on(",").join(methodsToPreserve));
129 private boolean classExists(String classStr) {
130 try {
131 Class.forName(classStr);
132 return true;
133 } catch (ClassNotFoundException e) {
134 prunedClasses.add(classStr);
135 return false;
139 public void addCallback(Set<String> kinds, String callbackType, String cls, String method) {
140 String clsMethod = String.format("%s:%s", cls, method);
141 if (kinds.isEmpty()) {
142 kinds = Collections.singleton("");
144 for (String kind : kinds) {
145 Multimap<String, String> kindMap = callbacks.get(kind);
146 if (kindMap == null) {
147 kindMap = LinkedHashMultimap.create();
148 callbacks.put(kind, kindMap);
150 kindMap.put(callbackType, clsMethod);
152 methodsWithCallbacks.put(cls, method);
155 @Override
156 public String toString() {
157 return String.format(
158 "Datastore Callbacks: %s\nPruned Classes: %s", callbacks.toString(), prunedClasses);
161 public boolean hasCallback(String cls, String method) {
162 return methodsWithCallbacks.containsEntry(cls, method);