1   package com.atlassian.plugins.codegen;
2   
3   import java.util.Collection;
4   import java.util.Map;
5   
6   import com.google.common.base.Function;
7   import com.google.common.base.Joiner;
8   import com.google.common.base.Predicate;
9   import com.google.common.base.Predicates;
10  import com.google.common.collect.ImmutableList;
11  import com.google.common.collect.ImmutableSet;
12  import com.google.common.collect.Multimap;
13  import com.google.common.collect.Multimaps;
14  import com.google.common.collect.Ordering;
15  
16  import static com.google.common.base.Functions.toStringFunction;
17  import static com.google.common.base.Predicates.not;
18  import static com.google.common.collect.Iterables.concat;
19  import static com.google.common.collect.Iterables.filter;
20  import static com.google.common.collect.Iterables.isEmpty;
21  import static com.google.common.collect.Iterables.transform;
22  
23  /**
24   * Describes changes that should be applied to the project.  These may include changes
25   * to the POM, the plugin XML file, and any other files within the project.  Implementations
26   * of {@link com.atlassian.plugins.codegen.modules.PluginModuleCreator} return an instance
27   * of this class rather than performing the changes directly.
28   * <p>
29   * This class is immutable; all of its non-getter methods return new instances.
30   * <p>
31   * This class also contains static factory methods for all supported change types.
32   */
33  public final class PluginProjectChangeset
34  {
35      private static final PluginProjectChangeset EMPTY = new PluginProjectChangeset();
36      
37      private final ImmutableList<PluginProjectChange> changes;
38      
39      public static PluginProjectChangeset changeset()
40      {
41          return EMPTY;
42      }
43      
44      public PluginProjectChangeset()
45      {
46          this(ImmutableList.<PluginProjectChange>of());
47      }
48      
49      private PluginProjectChangeset(Iterable<PluginProjectChange> changes)
50      {
51          this.changes = ImmutableList.copyOf(changes);
52      }
53  
54      /**
55       * Returns all changes in the changeset.
56       */
57      public Iterable<PluginProjectChange> getItems()
58      {
59          return changes;
60      }
61      
62      /**
63       * Returns only the changes of the specified class.
64       */
65      public <T extends PluginProjectChange> Iterable<T> getItems(Class<T> itemClass)
66      {
67          return filter(changes, itemClass);
68      }
69      
70      /**
71       * Returns true if the changeset contains any items of the specified class.
72       */
73      public boolean hasItems(Class<? extends PluginProjectChange> itemClass)
74      {
75          return !isEmpty(getItems(itemClass));
76      }
77      
78      /**
79       * Returns a copy of this changeset with the specified item(s) added.
80       */
81      public PluginProjectChangeset with(PluginProjectChange... newChanges)
82      {
83          return new PluginProjectChangeset(concat(changes, ImmutableList.copyOf(newChanges)));
84      }
85  
86      /**
87       * Returns a copy of this changeset with the specified item(s) added.
88       */
89      public PluginProjectChangeset with(Iterable<? extends PluginProjectChange> newChanges)
90      {
91          return new PluginProjectChangeset(concat(changes, ImmutableList.copyOf(newChanges)));
92      }
93      
94      /**
95       * Returns a changeset consisting of this changeset plus all items from another changeset.
96       */
97      public PluginProjectChangeset with(PluginProjectChangeset other)
98      {
99          return new PluginProjectChangeset(concat(changes, other.changes));
100     }
101     
102     /**
103      * Returns the toString() description of every change in the changeset.
104      */
105     public Iterable<String> getAllChangeDescriptions()
106     {
107         return transform(changes, toStringFunction());
108     }
109     
110     /**
111      * Returns the toString() description of every change in the changeset, sorted by type,
112      * except for changes that implement {@link SummarizeAsGroup}, which will instead be counted.
113      */
114     public Iterable<String> getChangeDescriptionsOrSummaries()
115     {
116         Iterable<String> uniqueDescriptions = ImmutableSet.copyOf(transform(filter(changes, not(summarizable)), toStringFunction()));
117         Multimap<String, PluginProjectChange> summaries = Multimaps.index(changes, summarizableGroupName);
118         return concat(Ordering.<String>natural().sortedCopy(uniqueDescriptions),
119                       filter(transform(summaries.asMap().entrySet(), summaryDescription), Predicates.notNull()));
120     }
121     
122     @Override
123     public String toString()
124     {
125         return Joiner.on(",\n").join(getAllChangeDescriptions());
126     }
127     
128     private static Predicate<PluginProjectChange> summarizable = new Predicate<PluginProjectChange>()
129     {
130         public boolean apply(PluginProjectChange input)
131         {
132             return input instanceof SummarizeAsGroup;
133         }
134     };
135     
136     private static Function<PluginProjectChange, String> summarizableGroupName = new Function<PluginProjectChange, String>()
137     {
138         public String apply(PluginProjectChange input)
139         {
140             return (input instanceof SummarizeAsGroup) ? ((SummarizeAsGroup) input).getGroupName() : "";
141         }
142     }; 
143 
144     private static Function<Map.Entry<String, Collection<PluginProjectChange>>, String> summaryDescription =
145         new Function<Map.Entry<String, Collection<PluginProjectChange>>, String>()
146     {
147         public String apply(Map.Entry<String, Collection<PluginProjectChange>> input)
148         {
149             return (input.getKey().equals("")) ? null : (input.getKey() + ": " + input.getValue().size());
150         }
151     };
152 }