001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.openoffice;
018:
019: // J2SE dependencies
020: import java.util.Map;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.TimeZone;
025: import java.util.Calendar;
026: import java.util.GregorianCalendar;
027: import java.util.logging.Level;
028: import java.util.logging.Logger;
029: import java.util.logging.LogRecord;
030:
031: // OpenOffice dependencies
032: import com.sun.star.sheet.XAddIn;
033: import com.sun.star.util.Date;
034: import com.sun.star.lang.Locale;
035: import com.sun.star.lang.XServiceInfo;
036: import com.sun.star.lang.XServiceName;
037: import com.sun.star.beans.XPropertySet;
038: import com.sun.star.uno.AnyConverter;
039: import com.sun.star.lib.uno.helper.WeakBase;
040:
041: // Geotools dependencies
042: import org.geotools.resources.Utilities;
043: import org.geotools.util.logging.Logging;
044:
045: /**
046: * Base class for methods to export as formulas in the
047: * <A HREF="http://www.openoffice.org">OpenOffice</A> spread sheet.
048: *
049: * @since 2.2
050: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/openoffice/src/main/java/org/geotools/openoffice/Formulas.java $
051: * @version $Id: Formulas.java 27862 2007-11-12 19:51:19Z desruisseaux $
052: * @author Martin Desruisseaux
053: */
054: public abstract class Formulas extends WeakBase implements XAddIn,
055: XServiceName, XServiceInfo {
056: /**
057: * The logger to use for all message to log in this package.
058: */
059: private static final Logger LOGGER = Logging
060: .getLogger("org.geotools.openoffice");
061:
062: /**
063: * Factor for conversions of days to milliseconds.
064: * Used for date conversions as in {@link #toDate}.
065: */
066: protected static final long DAY_TO_MILLIS = 24 * 60 * 60 * 1000L;
067:
068: /**
069: * Informations about exported methods.
070: */
071: protected final Map/*<String,MethodInfo>*/methods = new HashMap/*<String,MethodInfo>*/();
072:
073: /**
074: * Locale attribute required by {@code com.sun.star.lang.XLocalizable} interface.
075: */
076: private Locale locale;
077:
078: /**
079: * The locale as an object from the standard Java SDK.
080: * Will be fetched only when first needed.
081: */
082: private transient java.util.Locale javaLocale;
083:
084: /**
085: * The calendar to uses for date conversions. Will be created only when first needed.
086: */
087: private transient Calendar calendar;
088:
089: /**
090: * Default constructor. Subclass constructors need to add entries in the {@link #methods} map.
091: */
092: protected Formulas() {
093: }
094:
095: /**
096: * Sets the locale to be used by this object.
097: */
098: public void setLocale(final Locale locale) {
099: this .locale = locale;
100: javaLocale = null;
101: }
102:
103: /**
104: * Returns the locale, which is used by this object.
105: */
106: public Locale getLocale() {
107: return locale;
108: }
109:
110: /**
111: * Returns the locale as an object from the Java standard SDK.
112: */
113: protected final java.util.Locale getJavaLocale() {
114: if (javaLocale == null) {
115: if (locale != null) {
116: String language = locale.Language;
117: if (language == null)
118: language = "";
119: String country = locale.Country;
120: if (country == null)
121: country = "";
122: String variant = locale.Variant;
123: if (variant == null)
124: variant = "";
125: javaLocale = new java.util.Locale(language, country,
126: variant);
127: } else {
128: javaLocale = java.util.Locale.getDefault();
129: }
130: }
131: return javaLocale;
132: }
133:
134: /**
135: * The service name that can be used to create such an object by a factory.
136: * This is defined as a field in the subclass with exactly the following signature:
137: *
138: * <blockquote><code>
139: * private static final String __serviceName;
140: * </code></blockquote>
141: */
142: public abstract String getServiceName();
143:
144: /**
145: * Provides the implementation name of the service implementation.
146: *
147: * @return Unique name of the implementation.
148: */
149: public String getImplementationName() {
150: return getClass().getName();
151: }
152:
153: /**
154: * Returns the programmatic name of the category the function belongs to.
155: * The category name is used to group similar functions together. The programmatic
156: * category name should always be in English, it is never shown to the user. It
157: * is usually one of the names listed in {@code com.sun.star.sheet.XAddIn} interface.
158: *
159: * @param function The exact name of a method within its interface.
160: * @return The category name the specified function belongs to.
161: */
162: public String getProgrammaticCategoryName(final String function) {
163: final MethodInfo info = (MethodInfo) methods.get(function);
164: return (info != null) ? info.category : "Add-In";
165: }
166:
167: /**
168: * Returns the user-visible name of the category the function belongs to.
169: * This is used when category names are shown to the user.
170: *
171: * @param function The exact name of a method within its interface.
172: * @return The user-visible category name the specified function belongs to.
173: */
174: public String getDisplayCategoryName(final String function) {
175: return getProgrammaticCategoryName(function);
176: }
177:
178: /**
179: * Returns the internal function name for an user-visible name. The user-visible
180: * name of a function is the name shown to the user. It may be translated to the
181: * {@linkplain #getLocale current language}, so it is never stored in files. It
182: * should be a single word and is used when entering or displaying formulas.
183: * <p>
184: * Attention: The method name contains a spelling error. Due to compatibility
185: * reasons the name cannot be changed.
186: *
187: * @param display The user-visible name of a function.
188: * @return The exact name of the method within its interface.
189: */
190: public String getProgrammaticFuntionName(final String display) {
191: for (final Iterator it = methods.entrySet().iterator(); it
192: .hasNext();) {
193: final Map.Entry/*<String,MethodInfo>*/entry = (Map.Entry) it
194: .next();
195: if (display.equals(((MethodInfo) entry.getValue()).display)) {
196: return (String) entry.getKey();
197: }
198: }
199: return "";
200: }
201:
202: /**
203: * Returns the user-visible function name for an internal name.
204: * The user-visible name of a function is the name shown to the user.
205: * It may be translated to the {@linkplain #getLocale current language},
206: * so it is never stored in files. It should be a single word and is used
207: * when entering or displaying formulas.
208: *
209: * @param function The exact name of a method within its interface.
210: * @return The user-visible name of the specified function.
211: */
212: public String getDisplayFunctionName(final String function) {
213: final MethodInfo info = (MethodInfo) methods.get(function);
214: return (info != null) ? info.display : "";
215: }
216:
217: /**
218: * Returns the description of a function. The description is shown to the user
219: * when selecting functions. It may be translated to the {@linkplain #getLocale
220: * current language}.
221: *
222: * @param function The exact name of a method within its interface.
223: * @return The description of the specified function.
224: */
225: public String getFunctionDescription(final String function) {
226: final MethodInfo info = (MethodInfo) methods.get(function);
227: return (info != null) ? info.description : "";
228: }
229:
230: /**
231: * Returns the user-visible name of the specified argument. The argument name is
232: * shown to the user when prompting for arguments. It should be a single word and
233: * may be translated to the {@linkplain #getLocale current language}.
234: *
235: * @param function The exact name of a method within its interface.
236: * @param argument The index of the argument (0-based).
237: * @return The user-visible name of the specified argument.
238: */
239: public String getDisplayArgumentName(final String function,
240: int argument) {
241: final MethodInfo info = (MethodInfo) methods.get(function);
242: if (info != null) {
243: argument <<= 1;
244: final String[] arguments = info.arguments;
245: if (argument >= 0 && argument < arguments.length) {
246: return arguments[argument];
247: }
248: }
249: return "";
250: }
251:
252: /**
253: * Returns the description of the specified argument. The argument description is
254: * shown to the user when prompting for arguments. It may be translated to the
255: * {@linkplain #getLocale current language}.
256: *
257: * @param function The exact name of a method within its interface.
258: * @param argument The index of the argument (0-based).
259: * @return The description of the specified argument.
260: */
261: public String getArgumentDescription(final String function,
262: int argument) {
263: final MethodInfo info = (MethodInfo) methods.get(function);
264: if (info != null) {
265: argument = (argument << 1) + 1;
266: final String[] arguments = info.arguments;
267: if (argument >= 0 && argument < arguments.length) {
268: return arguments[argument];
269: }
270: }
271: return "";
272: }
273:
274: /**
275: * Sets the timezone for time values to be provided to {@link #toDate}.
276: * If this method is never invoked, then the default timezone is the locale one.
277: */
278: protected void setTimeZone(final String timezone) {
279: final TimeZone tz = TimeZone.getTimeZone(timezone);
280: if (calendar == null) {
281: calendar = new GregorianCalendar(tz);
282: } else {
283: calendar.setTimeZone(tz);
284: }
285: }
286:
287: /**
288: * Returns the spreadsheet epoch. The timezone is the one specified during the
289: * last invocation of {@link #setTimeZone}. The epoch is used for date conversions
290: * as in {@link #toDate}.
291: *
292: * @param xOptions Provided by OpenOffice.
293: * @return The spreedsheet epoch, always as a new Java Date object.
294: */
295: protected java.util.Date getEpoch(final XPropertySet xOptions) {
296: final Date date;
297: try {
298: date = (Date) AnyConverter.toObject(Date.class, xOptions
299: .getPropertyValue("NullDate"));
300: } catch (Exception e) {
301: // Les exception lancées par la ligne ci-dessus sont nombreuses...
302: reportException("getEpoch", e);
303: return null;
304: }
305: if (calendar == null) {
306: calendar = new GregorianCalendar();
307: }
308: calendar.clear();
309: calendar.set(date.Year, date.Month - 1, date.Day);
310: return calendar.getTime();
311: }
312:
313: /**
314: * Converts a date from a spreadsheet value to a Java {@link java.util.Date} object.
315: * The timezone is the one specified during the last invocation of {@link #setTimeZone}.
316: *
317: * @param xOptions Provided by OpenOffice.
318: * @param time The spreadsheet numerical value for a date, by default in the local timezone.
319: * @return The date as a Java object.
320: */
321: protected java.util.Date toDate(final XPropertySet xOptions,
322: final double time) {
323: final java.util.Date date = getEpoch(xOptions);
324: if (date != null) {
325: date.setTime(date.getTime()
326: + Math.round(time * DAY_TO_MILLIS));
327: }
328: return date;
329: }
330:
331: /**
332: * Converts a date from a Java {@link java.util.Date} object to a spreadsheet value.
333: * The timezone is the one specified during the last invocation of {@link #setTimeZone}.
334: */
335: protected double toDouble(final XPropertySet xOptions,
336: final java.util.Date time) {
337: final java.util.Date epoch = getEpoch(xOptions);
338: if (epoch != null) {
339: return (time.getTime() - epoch.getTime())
340: / (double) DAY_TO_MILLIS;
341: } else {
342: return Double.NaN;
343: }
344: }
345:
346: /**
347: * The string to returns when a formula don't have any value to return.
348: *
349: * @todo localize.
350: */
351: static String emptyString() {
352: return "(none)";
353: }
354:
355: /**
356: * Returns the minimal length of the specified arrays. In the special case where one array
357: * has a length of 1, we assume that this single element will be repeated for all elements
358: * in the other array.
359: */
360: static int getLength(final Object[] array1, final Object[] array2) {
361: if (array1 == null || array2 == null) {
362: return 0;
363: }
364: if (array1.length == 1)
365: return array2.length;
366: if (array2.length == 1)
367: return array1.length;
368: return Math.min(array1.length, array2.length);
369: }
370:
371: /**
372: * Returns the localized message from the specified exception. If no message is available,
373: * returns a default string. This method never returns a null value.
374: */
375: protected static String getLocalizedMessage(
376: final Throwable exception) {
377: final String message = exception.getLocalizedMessage();
378: if (message != null) {
379: return message;
380: }
381: return Utilities.getShortClassName(exception);
382: }
383:
384: /**
385: * Returns a table filled with {@link Double#NaN NaN} values. This method is invoked when
386: * an operation failed for a whole table.
387: *
388: * @since 2.3
389: */
390: protected static double[][] getFailure(final int rows,
391: final int cols) {
392: final double[][] dummy = new double[rows][];
393: for (int i = 0; i < rows; i++) {
394: final double[] row = new double[cols];
395: Arrays.fill(row, Double.NaN);
396: dummy[i] = row;
397: }
398: return dummy;
399: }
400:
401: /**
402: * Reports an exception. This is used if an exception occured in a method which can't returns
403: * a {@link String} object. This method log the stack trace at the FINE level. We don't use
404: * the WARNING level since this is not a program disfunction; the failure is probably caused
405: * by wrong user-specified parameters.
406: */
407: protected void reportException(final String method,
408: final Throwable exception) {
409: final LogRecord record = new LogRecord(Level.FINE,
410: getLocalizedMessage(exception));
411: record.setSourceClassName(getClass().getName());
412: record.setSourceMethodName(method);
413: record.setThrown(exception);
414: getLogger().log(record);
415: }
416:
417: /**
418: * Returns the logger to use for logging warnings. The default implementation returns the
419: * {@link org.geotools.openoffice} logger. Subclasses should override this method if they
420: * want to use a different logger.
421: */
422: protected Logger getLogger() {
423: return LOGGER;
424: }
425: }
|