1   package eu.fbk.dkm.premon.util;
2   
3   import java.io.BufferedReader;
4   import java.io.IOException;
5   import java.io.Reader;
6   import java.util.Arrays;
7   import java.util.Collection;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Objects;
11  import java.util.Set;
12  
13  import javax.annotation.Nullable;
14  
15  import com.google.common.base.Joiner;
16  import com.google.common.collect.HashMultimap;
17  import com.google.common.collect.ImmutableList;
18  import com.google.common.collect.ImmutableMap;
19  import com.google.common.collect.ImmutableSet;
20  import com.google.common.collect.Lists;
21  import com.google.common.collect.Multimap;
22  import com.google.common.collect.Ordering;
23  
24  import eu.fbk.rdfpro.util.IO;
25  
26  public final class Replacer {
27  
28      private final List<Rule> rules;
29  
30      private final Map<String, List<Rule>> index;
31  
32      public Replacer(final String location) throws IOException {
33          this(Rule.parse(location));
34      }
35  
36      public Replacer(final Rule... rules) {
37          this(Arrays.asList(rules));
38      }
39  
40      public Replacer(final Iterable<Rule> rules) {
41  
42          this.rules = Ordering.natural().sortedCopy(rules);
43  
44          final Multimap<String, Rule> multimap = HashMultimap.create();
45          for (final Rule rule : rules) {
46              multimap.put(rule.getSource(), rule);
47          }
48  
49          final ImmutableMap.Builder<String, List<Rule>> builder = ImmutableMap.builder();
50          for (final Map.Entry<String, Collection<Rule>> entry : multimap.asMap().entrySet()) {
51              final String source = entry.getKey();
52              final Rule[] sortedRules = entry.getValue().toArray(new Rule[entry.getValue().size()]);
53              Arrays.sort(sortedRules);
54              builder.put(source, ImmutableList.copyOf(sortedRules));
55          }
56          this.index = builder.build();
57      }
58  
59      public List<Rule> getRules() {
60          return this.rules;
61      }
62  
63      public List<Rule> getRules(@Nullable final String value, final String... context) {
64          if (value != null) {
65              final List<Rule> rules = this.index.get(value);
66              if (rules != null) {
67                  final List<Rule> result = Lists.newArrayList();
68                  for (final Rule rule : rules) {
69                      final String to = rule.apply(value, context);
70                      if (to != value) {
71                          result.add(rule);
72                      }
73                  }
74                  return result;
75              }
76          }
77          return ImmutableList.of();
78      }
79  
80      @Nullable
81      public String apply(@Nullable final String value, final String... context) {
82          return apply(value, Arrays.asList(context));
83      }
84  
85      @Nullable
86      public String apply(@Nullable final String value, final Iterable<String> context) {
87          if (value != null) {
88              final List<Rule> rules = this.index.get(value);
89              if (rules != null) {
90                  for (final Rule rule : rules) {
91                      final String to = rule.apply(value, context);
92                      if (to != value) {
93                          return to;
94                      }
95                  }
96              }
97          }
98          return value;
99      }
100 
101     @Override
102     public String toString() {
103         final StringBuilder builder = new StringBuilder();
104         String separator = "";
105         for (final String source : Ordering.natural().sortedCopy(this.index.keySet())) {
106             for (final Rule rule : this.index.get(source)) {
107                 builder.append(separator).append(rule);
108                 separator = "\n";
109             }
110         }
111         return builder.toString();
112     }
113 
114     public static final class Rule implements Comparable<Rule> {
115 
116         private final String source;
117 
118         private final String target;
119 
120         private final Set<String> context;
121 
122         public Rule(final String source, final String target,
123                 @Nullable final Iterable<String> context) {
124 
125             this.source = Objects.requireNonNull(source);
126             this.target = Objects.requireNonNull(target);
127             this.context = context == null ? ImmutableSet.of() : ImmutableSet.copyOf(context);
128         }
129 
130         public String getSource() {
131             return this.source;
132         }
133 
134         public String getTarget() {
135             return this.target;
136         }
137 
138         public Set<String> getContext() {
139             return this.context;
140         }
141 
142         @Nullable
143         public String apply(@Nullable final String value, final String... context) {
144             return apply(value, Arrays.asList(context));
145         }
146 
147         @Nullable
148         public String apply(@Nullable final String value, final Iterable<String> context) {
149             if (this.source.equals(value)) {
150                 int count = 0;
151                 for (final String s : context) {
152                     if (this.context.contains(s)) {
153                         ++count;
154                     }
155                     if (count == this.context.size()) {
156                         return this.target;
157                     }
158                 }
159             }
160             return value;
161         }
162 
163         @Override
164         public int compareTo(final Rule other) {
165             int result = this.source.compareTo(other.source);
166             if (result == 0) {
167                 result = -this.context.size() + other.context.size();
168                 if (result == 0) {
169                     final Ordering<String> o = Ordering.natural();
170                     result = Joiner.on('|').join(o.sortedCopy(this.context))
171                             .compareTo(Joiner.on('|').join(o.sortedCopy(other.context)));
172                     if (result == 0) {
173                         return this.target.compareTo(other.target);
174                     }
175                 }
176             }
177             return result;
178         }
179 
180         @Override
181         public boolean equals(final Object object) {
182             if (object == this) {
183                 return true;
184             }
185             if (!(object instanceof Rule)) {
186                 return false;
187             }
188             final Rule other = (Rule) object;
189             return this.source.equals(other.source) && this.target.equals(other.target)
190                     && this.context.equals(other.context);
191         }
192 
193         @Override
194         public int hashCode() {
195             return Objects.hash(this.source, this.target, this.context);
196         }
197 
198         @Override
199         public String toString() {
200             return this.source + " -> " + this.target + " { "
201                     + Joiner.on(" ").join(Ordering.natural().sortedCopy(this.context)) + " }";
202         }
203 
204         public static List<Rule> parse(final String location) throws IOException {
205             try (Reader reader = IO.utf8Reader(IO.buffer(IO.read(location)))) {
206                 return parse(reader);
207             }
208         }
209 
210         public static List<Rule> parse(final Reader reader) throws IOException {
211 
212             final BufferedReader in = reader instanceof BufferedReader ? (BufferedReader) reader
213                     : new BufferedReader(reader);
214 
215             final List<Rule> rules = Lists.newArrayList();
216             String line;
217             int lineIndex = 0;
218             while ((line = in.readLine()) != null) {
219                 ++lineIndex;
220                 line = line.trim();
221                 if (line.startsWith("#") || line.isEmpty()) {
222                     continue;
223                 }
224                 try {
225                     final int index = line.indexOf("->");
226                     final int index2 = line.indexOf('{');
227                     final int index3 = line.indexOf('}');
228                     final String source = line.substring(0, index).trim();
229                     final String target = line.substring(index + 2,
230                             index2 < 0 ? line.length() : index2).trim();
231                     final List<String> context = Lists.newArrayList();
232                     if (index2 > 0 && index3 > index2) {
233                         for (final String token : line.substring(index2 + 1, index3).split("\\s+")) {
234                             if (!token.isEmpty()) {
235                                 context.add(token);
236                             }
237                         }
238                     }
239                     rules.add(new Rule(source, target, context));
240                 } catch (final Throwable ex) {
241                     throw new IOException("Illegal replacement rule definition (line " + lineIndex
242                             + "): " + line);
243                 }
244             }
245             return rules;
246         }
247 
248     }
249 
250 }