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
;
20 import java
.util
.Properties
;
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.";
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
40 static final String FORMAT_VERSION_PROPERTY
= "DatastoreCallbacksFormatVersion";
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();
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();
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();
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)}
72 final Set
<String
> prunedClasses
= Sets
.newHashSet();
75 * @param inputStream a (possibly {@code null} stream from which we can read
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
);
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
);
124 props
.setProperty(propName
, Joiner
.on(",").join(methodsToPreserve
));
129 private boolean classExists(String classStr
) {
131 Class
.forName(classStr
);
133 } catch (ClassNotFoundException e
) {
134 prunedClasses
.add(classStr
);
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
);
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
);