1   package org.daisy.pipeline.braille.common;
2   
3   import java.io.InputStream;
4   import java.net.URL;
5   import java.util.HashMap;
6   import java.util.Iterator;
7   import java.util.Locale;
8   import java.util.Map;
9   import java.util.NoSuchElementException;
10  import java.util.Properties;
11  
12  import com.google.common.base.Function;
13  import com.google.common.base.Optional;
14  import com.google.common.collect.ImmutableMap;
15  import static com.google.common.collect.Iterables.concat;
16  import static com.google.common.collect.Iterables.transform;
17  
18  import org.daisy.pipeline.braille.common.util.Function2;
19  import org.daisy.pipeline.braille.common.util.Iterables;
20  
21  public interface Provider<Q,X> {
22  	
23  	/**
24  	 * Get a collection of objects based on a query.
25  	 * @param query is assumed to be immutable
26  	 * @return The objects for the query, in order of best match.
27  	 */
28  	public Iterable<X> get(Q query);
29  	
30  	/* ================== */
31  	/*       UTILS        */
32  	/* ================== */
33  	
34  	public static abstract class util {
35  		
36  		/* ------- */
37  		/* empty() */
38  		/* ------- */
39  		
40  		public static <Q,X> Provider<Q,X> empty() {
41  			return new Provider<Q,X>() {
42  				public Iterable<X> get(Q query) {
43  					return Optional.<X>absent().asSet();
44  				}
45  			};
46  		}
47  		
48  		/* --------- */
49  		/* memoize() */
50  		/* --------- */
51  		
52  		public interface MemoizingProvider<Q,X> extends Memoizing<Q,Iterable<X>>, Provider<Q,X> {}
53  		
54  		/**
55  		 * provider.get(query) must not mutate query
56  		 */
57  		public static <Q,X> MemoizingProvider<Q,X> memoize(final Provider<Q,X> provider) {
58  			return new Memoize<Q,X>() {
59  				protected Iterable<X> _get(Q query) {
60  					return Iterables.memoize(provider.get(query));
61  				}
62  			};
63  		}
64  		
65  		public static abstract class Memoize<Q,X> extends Memoizing.util.AbstractMemoizing<Q,Iterable<X>> implements MemoizingProvider<Q,X> {
66  			protected Memoize() {
67  				super();
68  			}
69  			protected Memoize(Memoize<Q,X> shareCacheWith) {
70  				super(shareCacheWith);
71  			}
72  			protected abstract Iterable<X> _get(Q query);
73  			public Iterable<X> get(Q query) {
74  				return apply(query);
75  			}
76  			protected final Iterable<X> _apply(Q query) {
77  				return _get(query);
78  			}
79  		}
80  		
81  		/* --------------------- */
82  		/* SimpleMappingProvider */
83  		/* --------------------- */
84  		
85  		public static abstract class SimpleMappingProvider<Q,X> implements Provider<Q,X> {
86  			private Map<Q,String> map;
87  			public abstract Q parseKey(String key);
88  			public abstract X parseValue(String value);
89  			public SimpleMappingProvider(URL properties) {
90  				map = readProperties(properties);
91  			}
92  			public Iterable<X> get(Q query) {
93  				String value = map.get(query);
94  				if (value != null)
95  					return Optional.fromNullable(parseValue(value)).asSet();
96  				return Optional.<X>absent().asSet();
97  			}
98  			private Map<Q,String> readProperties(URL url) {
99  				Map<Q,String> map = new HashMap<Q,String>();
100 				try {
101 					url.openConnection();
102 					InputStream reader = url.openStream();
103 					Properties properties = new Properties();
104 					properties.loadFromXML(reader);
105 					for (String key : properties.stringPropertyNames()) {
106 						Q query = parseKey(key);
107 						if (!map.containsKey(query))
108 							map.put(query, properties.getProperty(key)); }
109 					reader.close();
110 					return new ImmutableMap.Builder<Q,String>().putAll(map).build(); }
111 				catch (Exception e) {
112 					throw new RuntimeException("Could not read properties file " + url, e); }
113 			}
114 		}
115 		
116 		/* ---------- */
117 		/* dispatch() */
118 		/* ---------- */
119 		
120 		public static <Q,X> Provider<Q,X> dispatch(final Iterable<? extends Provider<Q,X>> dispatch) {
121 			return new Dispatch<Q,X>() {
122 				@SuppressWarnings(
123 					"unchecked" // safe cast to Iterable<Provider<Q,X>>
124 				)
125 				public Iterable<Provider<Q,X>> dispatch() {
126 					return (Iterable<Provider<Q,X>>)dispatch;
127 				}
128 			};
129 		}
130 		
131 		public static abstract class Dispatch<Q,X> implements Provider<Q,X> {
132 			public abstract Iterable<Provider<Q,X>> dispatch();
133 			public Iterable<X> get(final Q query) {
134 				return concat(transform(
135 					dispatch(),
136 					new Function<Provider<Q,X>,Iterable<X>>() {
137 						public Iterable<X> apply(Provider<Q,X> provider) {
138 							return provider.get(query); }}));
139 			}
140 		}
141 		
142 		/* ------------ */
143 		/* varyLocale() */
144 		/* ------------ */
145 		
146 		public static <X> Provider<Locale,X> varyLocale(final Provider<Locale,X> delegate) {
147 			return new VaryLocale<Locale,X>() {
148 				public Iterable<X> _get(Locale locale) {
149 					return delegate.get(locale);
150 				}
151 			};
152 		}
153 		
154 		public static <Q,X> Provider<Q,X> varyLocale(final Provider<Q,X> delegate,
155 		                                             final Function<Q,Locale> getLocale,
156 		                                             final Function2<Q,Locale,Q> assocLocale) {
157 			return new VaryLocale<Q,X>() {
158 				public Iterable<? extends X> _get(Q query) {
159 					return delegate.get(query);
160 				}
161 				public Locale getLocale(Q query) {
162 					return getLocale.apply(query);
163 				}
164 				public Q assocLocale(Q query, Locale locale) {
165 					return assocLocale.apply(query, locale);
166 				}
167 			};
168 		}
169 		
170 		public static abstract class VaryLocale<Q,X> implements Provider<Q,X> {
171 			public abstract Iterable<? extends X> _get(Q query);
172 			public abstract Locale getLocale(Q query);
173 			/**
174 			 * @param query must not be mutated
175 			 * @param locale
176 			 * @return a new query with the locale feature "added"
177 			 */
178 			public abstract Q assocLocale(Q query, Locale locale);
179 			public Locale getLocale(Locale query) {
180 				return query;
181 			}
182 			public Locale assocLocale(Locale query, Locale locale) {
183 				return locale;
184 			}
185 			public final Iterable<X> get(final Q query) {
186 				return new Iterable<X>() {
187 					public Iterator<X> iterator() {
188 						return new Iterator<X>() {
189 							Iterator<? extends X> next = null;
190 							int tryNext = 1;
191 							Locale locale = getLocale(query);
192 							public boolean hasNext() {
193 								while (next == null || !next.hasNext()) {
194 									switch (tryNext) {
195 									case 1:
196 										tryNext++;
197 										next = _get(query).iterator();
198 										if (locale == null || "".equals(locale.toString()))
199 											tryNext = 4;
200 										break;
201 									case 2:
202 										tryNext++;
203 										if (!"".equals(locale.getVariant()))
204 											next = _get(assocLocale(query, new Locale(locale.getLanguage(), locale.getCountry()))).iterator();
205 										break;
206 									case 3:
207 										tryNext++;
208 										if (!"".equals(locale.getCountry()))
209 											next = _get(assocLocale(query, new Locale(locale.getLanguage()))).iterator();
210 										break;
211 									case 4:
212 										tryNext++;
213 										next = fallback(query).iterator();
214 										break;
215 									default:
216 										return false; }}
217 								return true;
218 							}
219 							public X next() {
220 								if (!hasNext()) throw new NoSuchElementException();
221 								return next.next();
222 							}
223 							public void remove() {
224 								throw new UnsupportedOperationException();
225 							}
226 						};
227 					}
228 				};
229 			}
230 			public Iterable<X> fallback(Q query) {
231 				return Optional.<X>absent().asSet();
232 			}
233 		}
234 	}
235 }