View Javadoc

1   /*
2    *   Magic-Project is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package net.sf.magicproject.database;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.net.URL;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import net.sf.magicproject.clickable.targetable.card.CardModel;
34  import net.sf.magicproject.database.data.TranslatableData;
35  import net.sf.magicproject.database.propertyconfig.PropertyConfig;
36  import net.sf.magicproject.database.propertyconfig.PropertyProxyConfig;
37  import net.sf.magicproject.expression.IntValue;
38  import net.sf.magicproject.token.IdConst;
39  import net.sf.magicproject.tools.MToolKit;
40  import net.sf.magicproject.ui.i18n.LanguageManager;
41  import net.sf.magicproject.xml.XmlParser;
42  import net.sf.magicproject.xml.XmlParser.Node;
43  
44  import org.apache.commons.lang.StringUtils;
45  import org.xml.sax.SAXException;
46  
47  /***
48   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
49   * @author <a href="mailto:kismet-sl@users.sourceforge.net">Stefano "Kismet"
50   *         Lenzi</a>
51   * @since 0.90
52   */
53  public class Proxy {
54  
55  	/***
56  	 * Private decalration of this proxy.
57  	 */
58  	private final Map<String, Map<String, String>> aliases;
59  
60  	/***
61  	 * The stream base of stream urls.
62  	 */
63  	private String streamBaseUrl;
64  
65  	/***
66  	 * The URL where data would be build from.
67  	 */
68  	private List<UrlTokenizer> streams;
69  
70  	/***
71  	 * The language of this proxy.
72  	 */
73  	private String language;
74  
75  	/***
76  	 * The encoding of stream retrieved from the proxy URL.
77  	 */
78  	private String encoding;
79  
80  	/***
81  	 * The proxy's name as displayed in the GUI menus
82  	 */
83  	private String name;
84  
85  	/***
86  	 * The proxy web site providing the data. Is only here for information
87  	 * purpose.
88  	 */
89  	private String home;
90  
91  	/***
92  	 * The pictures configuration of this stream.
93  	 */
94  	public List<PictureConfiguration> pictures = new ArrayList<PictureConfiguration>();
95  
96  	/***
97  	 * The properties managed by this proxy.
98  	 */
99  	private List<PropertyConfig> properties;
100 
101 	/***
102 	 * The XML name of this proxy. The name corresponds to the XML file definition
103 	 * of this proxy
104 	 */
105 	private String xmlName;
106 
107 	/***
108 	 * The proxy's name as displayed in the GUI menus
109 	 * 
110 	 * @return The proxy's name as displayed in the GUI menus
111 	 */
112 	public String getName() {
113 		return name;
114 	}
115 
116 	@Override
117 	public String toString() {
118 		return getName();
119 	}
120 
121 	/***
122 	 * Return the XML name of this proxy. The name corresponds to the XML file
123 	 * definition of this proxy
124 	 * 
125 	 * @return the XML name of this proxy.
126 	 */
127 	public String getXmlName() {
128 		return xmlName;
129 	}
130 
131 	/***
132 	 * Create a new instance of this class.
133 	 * 
134 	 * @param xmlFile
135 	 *          the definition file of this proxy.
136 	 * @throws IOException
137 	 *           If some other I/O error occurs
138 	 * @throws SAXException
139 	 *           If some XML parse error occurs
140 	 */
141 	public Proxy(File xmlFile) throws IOException, SAXException {
142 		final XmlParser parser = new XmlParser();
143 		final Node config = parser.parse(new FileInputStream(xmlFile));
144 		xmlName = StringUtils.removeEnd(xmlFile.getName().toLowerCase(), ".xml");
145 		name = config.getAttribute("name");
146 		encoding = config.getAttribute("encoding");
147 		language = config.getAttribute("language");
148 		home = config.getAttribute("home");
149 
150 		final List<?> pictures = config.get("pictures").getNodes("picture");
151 		for (int i = 0; i < pictures.size(); i++) {
152 			final Node pictureStream = (Node) pictures.get(i);
153 			this.pictures.add(new PictureConfiguration(
154 					new UrlTokenizer(pictureStream), pictureStream.getAttribute("base")));
155 		}
156 
157 		// read private aliases
158 		final Node aliases = config.get("alias");
159 		this.aliases = new HashMap<String, Map<String, String>>();
160 		if (aliases != null) {
161 			List<Node> nodes = aliases.getNodes("alias");
162 			for (int i = nodes.size(); i-- > 0;) {
163 				final Node alias = nodes.get(i);
164 				Map<String, String> nameSpace = this.aliases.get(alias
165 						.getAttribute("property"));
166 				if (nameSpace == null) {
167 					nameSpace = new HashMap<String, String>();
168 					this.aliases.put(alias.getAttribute("property"), nameSpace);
169 				}
170 				nameSpace.put(alias.getAttribute("local-value").toLowerCase(), alias
171 						.getAttribute("ref"));
172 			}
173 		}
174 
175 		// read streams configurations
176 		Node dataConfig = config.get("data");
177 		Node streamConfig = dataConfig.get("streams");
178 		if (streamConfig != null) {
179 			streamBaseUrl = streamConfig.getAttribute("base");
180 			final List<Node> streamsNode = streamConfig.getNodes("stream");
181 			streams = new ArrayList<UrlTokenizer>(streamsNode.size());
182 			for (int i = 0; i < streamsNode.size(); i++) {
183 				streams.add(new UrlTokenizer(streamsNode.get(i)));
184 			}
185 			if (streams.isEmpty()) {
186 				throw new RuntimeException(
187 						"At least one stream configuration must be defined");
188 			}
189 
190 			// read properties configurations
191 			final List<Node> nodes = dataConfig.get("properties")
192 					.getNodes("property");
193 			properties = new ArrayList<PropertyConfig>(nodes.size());
194 			for (int i = 0; i < nodes.size(); i++) {
195 				properties.add(new PropertyProxyConfig(nodes.get(i)));
196 			}
197 		} else {
198 			streams = new ArrayList<UrlTokenizer>(0);
199 			properties = new ArrayList<PropertyConfig>(0);
200 		}
201 	}
202 
203 	/***
204 	 * @param cardModel
205 	 *          the card model.
206 	 * @param constraints
207 	 *          the constraints.
208 	 * @return the string read from one of the streams of this proxy.
209 	 * @throws IOException
210 	 *           If some other I/O error occurs
211 	 */
212 	private String getStringFromStream(CardModel cardModel,
213 			Map<String, String> constraints) throws IOException {
214 
215 		// Determine the best stream configuration
216 		int highestScore = -1;
217 		UrlTokenizer stream = null;
218 		for (int i = 0; i < streams.size(); i++) {
219 			int score = streams.get(i).getUrlScore(constraints);
220 			if (score > highestScore) {
221 				highestScore = score;
222 				stream = streams.get(i);
223 			}
224 		}
225 
226 		// No stream available for this proxy + card
227 		if (stream == null) {
228 			return null;
229 		}
230 
231 		// read stream from the built URL
232 		final URL mainPage = new URL(streamBaseUrl
233 				+ stream.getUrl(cardModel, constraints, this));
234 		final StringBuilder res = new StringBuilder(2000);
235 		final InputStream proxyStream;
236 		try {
237 			proxyStream = MToolKit.getHttpConnection(mainPage).getInputStream();
238 		} catch (Throwable e) {
239 			// Error during the IP get
240 			throw new IOException(LanguageManager.getString("error.stream.null"));
241 		}
242 		if (proxyStream == null) {
243 			// Error during the IP get
244 			throw new IOException(LanguageManager.getString("error.stream.null"));
245 		}
246 		final BufferedReader br = new BufferedReader(new InputStreamReader(
247 				proxyStream, encoding));
248 		String line = null;
249 		while ((line = br.readLine()) != null) {
250 			res.append(line);
251 		}
252 		return res.toString();
253 	}
254 
255 	/***
256 	 * Create a new DatabaseCard from the given CardModel. The best stream is
257 	 * determinated depending the given constraints. The assocaited picture is
258 	 * also will be downloaded and loaded only during the first display of this
259 	 * picture.
260 	 * 
261 	 * @param cardModel
262 	 *          the object containing the card name.
263 	 * @param constraints
264 	 *          set of constraints
265 	 * @return a new DatabaseCard from the given CardModel.
266 	 * @throws IOException
267 	 *           If some other I/O error occurs
268 	 */
269 	public DatabaseCard getDatabaseFromStream(CardModel cardModel,
270 			Map<String, String> constraints) throws IOException {
271 		final DatabaseCard databaseCard = new DatabaseCard(cardModel, this,
272 				DatabaseFactory.pictureProxies);
273 
274 		// Find the best stream
275 		final String stream = getStringFromStream(cardModel, constraints);
276 
277 		// Add parsed properties managed by this proxy
278 		PropertyProxyConfig.values.clear();
279 		PropertyProxyConfig.values.put("%last-offset", new IntValue(0));
280 		for (PropertyConfig property : properties) {
281 			final TranslatableData data = property.parseProperty(cardModel
282 					.getCardName(), stream, this);
283 			if (data != null) {
284 				databaseCard.add(data);
285 			}
286 		}
287 
288 		return databaseCard;
289 	}
290 
291 	/***
292 	 * Return the encoding of stream retrieved from the proxy URL.<br>
293 	 * Unreferenced method, but called with reflection.
294 	 * 
295 	 * @return the encoding of stream retrieved from the proxy URL.
296 	 */
297 	public String getEncoding() {
298 		return encoding;
299 	}
300 
301 	/***
302 	 * Return the proxy web site providing the data. Is only here for information
303 	 * purpose.<br>
304 	 * Unreferenced method, but called with reflection.
305 	 * 
306 	 * @return the proxy web site providing the data.
307 	 */
308 	public String getHome() {
309 		return home;
310 	}
311 
312 	/***
313 	 * Return the language of this proxy.<br>
314 	 * Unreferenced method, but called with reflection.
315 	 * 
316 	 * @return the language of this proxy.
317 	 */
318 	public String getLanguage() {
319 		return language;
320 	}
321 
322 	/***
323 	 * Return the referenced value of this proxy. If this proxy do not define the
324 	 * namaed alias, return the given <param>localValue</param>.
325 	 * 
326 	 * @param nameSpace
327 	 *          the name space (property)
328 	 * @param localValue
329 	 *          the private-proxy value
330 	 * @return the referenced value of this proxy.
331 	 */
332 	public String getGlobalValueFromLocal(String nameSpace, String localValue) {
333 		if (localValue.length() > 400) {
334 			// This property contains some invalid data
335 			return "invalid data";
336 		}
337 
338 		final Map<String, String> alias = aliases.get(nameSpace);
339 		if (alias != null && alias.containsKey(localValue.toLowerCase())) {
340 			return alias.get(localValue.toLowerCase());
341 		}
342 		return localValue;
343 	}
344 
345 	/***
346 	 * Return the referenced value of this proxy. If this proxy do not define the
347 	 * namaed alias, return the given <param>localValue</param>.
348 	 * 
349 	 * @param nameSpace
350 	 * @param localValue
351 	 * @return the referenced value of this proxy.
352 	 */
353 	public String getLocalValueFromGlobal(String nameSpace, String localValue) {
354 		final Map<String, String> alias = aliases.get(nameSpace);
355 		if (alias != null && alias.containsValue(localValue.toLowerCase())) {
356 			for (String key : alias.keySet()) {
357 				if (alias.get(key).equals(localValue.toLowerCase())) {
358 					return key;
359 				}
360 			}
361 		}
362 		return localValue;
363 	}
364 
365 	/***
366 	 * Return a list of remote picture paths considering proxy, properties and the
367 	 * given card. The returned order corresponds to the priority.
368 	 * 
369 	 * @param cardModel
370 	 *          the card model.
371 	 * @param data
372 	 *          the translated data.
373 	 * @return a list of remote picture paths.
374 	 */
375 	public List<String> getRemotePictures(CardModel cardModel,
376 			Map<String, TranslatableData> data) {
377 		List<String> res = new ArrayList<String>(pictures.size());
378 		for (PictureConfiguration p : pictures) {
379 			try {
380 				final String localUrl = p.getPictureUrl().getUrl(cardModel, data, this);
381 				res.add(p.getProxyBaseUrl() + "/" + localUrl);
382 			} catch (Exception e) {
383 				// Ignore this error, we'll not add this url
384 				res.add(null);
385 			}
386 		}
387 		return res;
388 	}
389 
390 	/***
391 	 * Return a list of local picture paths considering proxy, properties and the
392 	 * given card. The returned order corresponds to the priority.
393 	 * 
394 	 * @param cardModel
395 	 *          the card model.
396 	 * @param data
397 	 *          the translated data.
398 	 * @return a list of local picture paths.
399 	 */
400 	public List<String> getLocalPictures(CardModel cardModel,
401 			Map<String, TranslatableData> data) {
402 		List<String> res = new ArrayList<String>(pictures.size());
403 		for (PictureConfiguration p : pictures) {
404 			try {
405 				final String localUrl = p.getPictureUrl().getUrl(cardModel, data, this);
406 				res.add(MToolKit.getTbsPicture(IdConst.PROXIES_LOCATION + "/"
407 						+ getValidPath(getXmlName()) + "/" + localUrl, false));
408 			} catch (Exception e) {
409 				// Ignore this error, we'll not add this url
410 				res.add(null);
411 			}
412 
413 		}
414 		return res;
415 	}
416 
417 	/***
418 	 * Simple method returning a suitable path for Windows, Linux,...
419 	 * 
420 	 * @param path
421 	 *          any non null string
422 	 * @return a suitable path for Windows, Linux,...
423 	 */
424 	private String getValidPath(String path) {
425 		return path.replace('/', '_').replace(':', '_').replace('*', '_').replace(
426 				'//', '_').replace('%', '_').replace('"', '_').replace('\'', '_')
427 				.replace('&', '_').replace('$', '_').replace('~', '_');
428 	}
429 
430 }