KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > olap > Util


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/olap/Util.java#92 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2001-2002 Kana Software, Inc.
7 // Copyright (C) 2001-2007 Julian Hyde and others
8 // All Rights Reserved.
9 // You must accept the terms of that agreement to use this software.
10 //
11 // jhyde, 6 August, 2001
12 */

13
14 package mondrian.olap;
15
16 import org.apache.log4j.Logger;
17 import org.eigenbase.xom.XOMUtil;
18
19 import java.io.File JavaDoc;
20 import java.io.PrintWriter JavaDoc;
21 import java.io.BufferedReader JavaDoc;
22 import java.io.Reader JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.InputStreamReader JavaDoc;
25 import java.io.StringWriter JavaDoc;
26 import java.net.MalformedURLException JavaDoc;
27 import java.net.URL JavaDoc;
28 import java.util.*;
29 import java.util.regex.Matcher JavaDoc;
30 import java.util.regex.Pattern JavaDoc;
31
32 import mondrian.olap.fun.*;
33 import mondrian.olap.type.Type;
34 import mondrian.resource.MondrianResource;
35 import mondrian.mdx.*;
36 import mondrian.util.UtilCompatible;
37
38 /**
39  * Utility functions used throughout mondrian. All methods are static.
40  *
41  * @author jhyde
42  * @since 6 August, 2001
43  * @version $Id: //open/mondrian/src/main/mondrian/olap/Util.java#92 $
44  */

45 public class Util extends XOMUtil {
46
47     public static final String JavaDoc nl = System.getProperty("line.separator");
48
49     private static final Logger LOGGER = Logger.getLogger(Util.class);
50
51     /**
52      * Placeholder which indicates a value NULL.
53      */

54     public static final Object JavaDoc nullValue = new Double JavaDoc(FunUtil.DoubleNull);
55
56     /**
57      * Placeholder which indicates an EMPTY value.
58      */

59     public static final Object JavaDoc EmptyValue = new Double JavaDoc(FunUtil.DoubleEmpty);
60
61     /**
62      * Cumulative time spent accessing the database.
63      */

64     private static long databaseMillis = 0;
65
66     /**
67      * Random number generator to provide seed for other random number
68      * generators.
69      */

70     private static final Random metaRandom =
71             createRandom(MondrianProperties.instance().TestSeed.get());
72
73     /**
74      * Whether we are running a version of Java before 1.5.
75      *
76      * <p>If this variable is true, we will be running retroweaver. Retroweaver
77      * has some problems involving {@link java.util.EnumSet}.
78      */

79     public static final boolean PreJdk15 =
80         System.getProperty("java.version").startsWith("1.4");
81
82     private static final UtilCompatible compatible;
83     static {
84         String JavaDoc className;
85         if (PreJdk15) {
86             className = "mondrian.util.UtilCompatibleJdk14";
87         } else {
88             className = "mondrian.util.UtilCompatibleJdk15";
89         }
90         try {
91             Class JavaDoc<UtilCompatible> clazz =
92                 (Class JavaDoc<UtilCompatible>) Class.forName(className);
93             compatible = clazz.newInstance();
94         } catch (ClassNotFoundException JavaDoc e) {
95             throw Util.newInternal(e, "Could not load '" + className + "'");
96         } catch (InstantiationException JavaDoc e) {
97             throw Util.newInternal(e, "Could not load '" + className + "'");
98         } catch (IllegalAccessException JavaDoc e) {
99             throw Util.newInternal(e, "Could not load '" + className + "'");
100         }
101     }
102
103     public static boolean isNull(Object JavaDoc o) {
104         return o == null || o == nullValue;
105     }
106
107     /**
108      * Returns whether a list is strictly sorted.
109      *
110      * @param list List
111      * @return whether list is sorted
112      */

113     public static <T> boolean isSorted(List<T> list) {
114         T prev = null;
115         for (T t : list) {
116             if (prev != null &&
117                 ((Comparable JavaDoc<T>) prev).compareTo(t) >= 0) {
118                 return false;
119             }
120             prev = t;
121         }
122         return true;
123     }
124
125     /**
126      * Encodes string for MDX (escapes ] as ]] inside a name).
127      */

128     public static String JavaDoc mdxEncodeString(String JavaDoc st) {
129         StringBuilder JavaDoc retString = new StringBuilder JavaDoc(st.length() + 20);
130         for (int i = 0; i < st.length(); i++) {
131             char c = st.charAt(i);
132             if ((c == ']') &&
133                 ((i+1) < st.length()) &&
134                 (st.charAt(i+1) != '.')) {
135
136                 retString.append(']'); //escaping character
137
}
138             retString.append(c);
139         }
140         return retString.toString();
141     }
142
143     /**
144      * Converts a string into a double-quoted string.
145      */

146     public static String JavaDoc quoteForMdx(String JavaDoc val) {
147         StringBuilder JavaDoc buf = new StringBuilder JavaDoc(val.length()+20);
148         buf.append("\"");
149
150         String JavaDoc s0 = replace(val, "\"", "\"\"");
151         buf.append(s0);
152
153         buf.append("\"");
154         return buf.toString();
155     }
156
157     /**
158      * Return string quoted in [...]. For example, "San Francisco" becomes
159      * "[San Francisco]"; "a [bracketed] string" becomes
160      * "[a [bracketed]] string]".
161      */

162     public static String JavaDoc quoteMdxIdentifier(String JavaDoc id) {
163         StringBuilder JavaDoc buf = new StringBuilder JavaDoc(id.length() + 20);
164         quoteMdxIdentifier(id, buf);
165         return buf.toString();
166     }
167
168     public static void quoteMdxIdentifier(String JavaDoc id, StringBuilder JavaDoc buf) {
169         buf.append('[');
170         int start = buf.length();
171         buf.append(id);
172         replace(buf, start, "]", "]]");
173         buf.append(']');
174     }
175
176     /**
177      * Return identifiers quoted in [...].[...]. For example, {"Store", "USA",
178      * "California"} becomes "[Store].[USA].[California]".
179      */

180     public static String JavaDoc quoteMdxIdentifier(String JavaDoc[] ids) {
181         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(64);
182         quoteMdxIdentifier(ids, sb);
183         return sb.toString();
184     }
185
186     public static void quoteMdxIdentifier(String JavaDoc[] ids, StringBuilder JavaDoc sb) {
187         for (int i = 0; i < ids.length; i++) {
188             if (i > 0) {
189                 sb.append('.');
190             }
191             quoteMdxIdentifier(ids[i], sb);
192         }
193     }
194
195     /**
196      * Returns true if two objects are equal, or are both null.
197      */

198     public static boolean equals(Object JavaDoc s, Object JavaDoc t) {
199         return (s == null) ? (t == null) : s.equals(t);
200     }
201
202     /**
203      * Returns true if two strings are equal, or are both null.
204      *
205      * <p>The result is not affected by
206      * {@link MondrianProperties#CaseSensitive the case sensitive option}; if
207      * you wish to compare names, use {@link #equalName(String, String)}.
208      */

209     public static boolean equals(String JavaDoc s, String JavaDoc t) {
210         return equals((Object JavaDoc) s, (Object JavaDoc) t);
211     }
212
213     /**
214      * Returns whether two names are equal.
215      * Takes into account the
216      * {@link MondrianProperties#CaseSensitive case sensitive option}.
217      * Names may be null.
218      */

219     public static boolean equalName(String JavaDoc s, String JavaDoc t) {
220         if (s == null) {
221             return t == null;
222         }
223         boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get();
224         return caseSensitive ? s.equals(t) : s.equalsIgnoreCase(t);
225     }
226
227     /**
228      * Tests two strings for equality, optionally ignoring case.
229      *
230      * @param s First string
231      * @param t Second string
232      * @param matchCase Whether to perform case-sensitive match
233      * @return Whether strings are equal
234      */

235     public static boolean equal(String JavaDoc s, String JavaDoc t, boolean matchCase) {
236         return matchCase ? s.equals(t) : s.equalsIgnoreCase(t);
237     }
238
239     /**
240      * Compares two names.
241      * Takes into account the {@link MondrianProperties#CaseSensitive case
242      * sensitive option}.
243      * Names must not be null.
244      */

245     public static int compareName(String JavaDoc s, String JavaDoc t) {
246         boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get();
247         return caseSensitive ? s.compareTo(t) : s.compareToIgnoreCase(t);
248     }
249
250     /**
251      * Generates a normalized form of a name, for use as a key into a map.
252      * Returns the upper case name if
253      * {@link MondrianProperties#CaseSensitive} is true, the name unchanged
254      * otherwise.
255      */

256     public static String JavaDoc normalizeName(String JavaDoc s) {
257         return MondrianProperties.instance().CaseSensitive.get() ?
258                 s :
259                 s.toUpperCase();
260     }
261
262     /**
263      * Returns the result of ((Comparable) k1).compareTo(k2), with
264      * special-casing for the fact that Boolean only became
265      * comparable in JDK 1.5.
266      *
267      * @see Comparable#compareTo
268      */

269     public static int compareKey(Object JavaDoc k1, Object JavaDoc k2) {
270         if (k1 instanceof Boolean JavaDoc) {
271             // Luckily, "F" comes before "T" in the alphabet.
272
k1 = k1.toString();
273             k2 = k2.toString();
274         }
275         return ((Comparable JavaDoc) k1).compareTo(k2);
276     }
277
278     /**
279      * Returns a string with every occurrence of a seek string replaced with
280      * another.
281      */

282     public static String JavaDoc replace(String JavaDoc s, String JavaDoc find, String JavaDoc replace) {
283         // let's be optimistic
284
int found = s.indexOf(find);
285         if (found == -1) {
286             return s;
287         }
288         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(s.length() + 20);
289         int start = 0;
290         char[] chars = s.toCharArray();
291         final int step = find.length();
292         if (step == 0) {
293             // Special case where find is "".
294
sb.append(s);
295             replace(sb, 0, find, replace);
296         } else {
297             for (;;) {
298                 sb.append(chars, start, found-start);
299                 if (found == s.length()) {
300                     break;
301                 }
302                 sb.append(replace);
303                 start = found + step;
304                 found = s.indexOf(find, start);
305                 if (found == -1) {
306                     found = s.length();
307                 }
308             }
309         }
310         return sb.toString();
311     }
312
313     /**
314      * Replaces all occurrences of a string in a buffer with another.
315      *
316      * @param buf String buffer to act on
317      * @param start Ordinal within <code>find</code> to start searching
318      * @param find String to find
319      * @param replace String to replace it with
320      * @return The string buffer
321      */

322     public static StringBuilder JavaDoc replace(
323             StringBuilder JavaDoc buf,
324             int start,
325             String JavaDoc find, String JavaDoc replace) {
326         // Search and replace from the end towards the start, to avoid O(n ^ 2)
327
// copying if the string occurs very commonly.
328
int findLength = find.length();
329         if (findLength == 0) {
330             // Special case where the seek string is empty.
331
for (int j = buf.length(); j >= 0; --j) {
332                 buf.insert(j, replace);
333             }
334             return buf;
335         }
336         int k = buf.length();
337         while (k > 0) {
338             int i = buf.lastIndexOf(find, k);
339             if (i < start) {
340                 break;
341             }
342             buf.replace(i, i + find.length(), replace);
343             // Step back far enough to ensure that the beginning of the section
344
// we just replaced does not cause a match.
345
k = i - findLength;
346         }
347         return buf;
348     }
349
350     public static String JavaDoc[] explode(String JavaDoc s) {
351         if (!s.startsWith("[")) {
352             return new String JavaDoc[]{s};
353         }
354
355         List<String JavaDoc> list = new ArrayList<String JavaDoc>();
356         int i = 0;
357
358         while (i < s.length()) {
359             if (s.charAt(i) != '[') {
360                 throw MondrianResource.instance().MdxInvalidMember.ex(s);
361             }
362
363             int j = getEndIndex(s, i + 1);
364             if (j == -1) {
365                 throw MondrianResource.instance().MdxInvalidMember.ex(s);
366             }
367
368             list.add(replace(s.substring(i + 1, j), "]]", "]"));
369             i = j + 2;
370         }
371         return list.toArray(new String JavaDoc[list.size()]);
372     }
373
374     private static int getEndIndex(String JavaDoc s, int i) {
375         while (i < s.length()) {
376             char ch = s.charAt(i);
377             if (ch == ']') {
378                 if (i + 1 < s.length() && s.charAt(i + 1) == ']') { // found ]] => skip
379
i += 2;
380                 } else {
381                     return i;
382                 }
383             } else {
384                 i++;
385             }
386         }
387         return -1;
388     }
389
390     /**
391      * Converts an array of name parts {"part1", "part2"} into a single string
392      * "[part1].[part2]". If the names contain "]" they are escaped as "]]".
393      */

394     public static String JavaDoc implode(String JavaDoc[] names) {
395         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(64);
396         for (int i = 0; i < names.length; i++) {
397             if (i > 0) {
398                 sb.append(".");
399             }
400             quoteMdxIdentifier(names[i], sb);
401         }
402         return sb.toString();
403     }
404
405     public static String JavaDoc makeFqName(String JavaDoc name) {
406         return quoteMdxIdentifier(name);
407     }
408
409     public static String JavaDoc makeFqName(OlapElement parent, String JavaDoc name) {
410         if (parent == null) {
411             return Util.quoteMdxIdentifier(name);
412         } else {
413             StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
414             buf.append(parent.getUniqueName());
415             buf.append('.');
416             Util.quoteMdxIdentifier(name, buf);
417             return buf.toString();
418         }
419     }
420
421     public static String JavaDoc makeFqName(String JavaDoc parentUniqueName, String JavaDoc name) {
422         if (parentUniqueName == null) {
423             return quoteMdxIdentifier(name);
424         } else {
425             StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
426             buf.append(parentUniqueName);
427             buf.append('.');
428             Util.quoteMdxIdentifier(name, buf);
429             return buf.toString();
430         }
431     }
432
433     public static OlapElement lookupCompound(
434         SchemaReader schemaReader,
435         OlapElement parent,
436         String JavaDoc[] names,
437         boolean failIfNotFound,
438         int category)
439     {
440         return lookupCompound(
441             schemaReader, parent, names, failIfNotFound, category,
442             MatchType.EXACT);
443     }
444
445     /**
446      * Resolves a name such as
447      * '[Products]&#46;[Product Department]&#46;[Produce]' by resolving the
448      * components ('Products', and so forth) one at a time.
449      *
450      * @param schemaReader Schema reader, supplies access-control context
451      * @param parent Parent element to search in
452      * @param names Exploded compound name, such as {"Products",
453      * "Product Department", "Produce"}
454      * @param failIfNotFound If the element is not found, determines whether
455      * to return null or throw an error
456      * @param category Type of returned element, a {@link mondrian.olap.Category} value;
457      * {@link mondrian.olap.Category#Unknown} if it doesn't matter.
458      * @pre parent != null
459      * @post !(failIfNotFound && return == null)
460      * @see #explode
461      */

462     public static OlapElement lookupCompound(
463         SchemaReader schemaReader,
464         OlapElement parent,
465         String JavaDoc[] names,
466         boolean failIfNotFound,
467         int category,
468         MatchType matchType)
469     {
470
471         Util.assertPrecondition(parent != null, "parent != null");
472
473         if (LOGGER.isDebugEnabled()) {
474             StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
475             buf.append("Util.lookupCompound: ");
476             buf.append("parent.name=");
477             buf.append(parent.getName());
478             buf.append(", category=");
479             buf.append(Category.instance.getName(category));
480             buf.append(", names=");
481             quoteMdxIdentifier(names, buf);
482             LOGGER.debug(buf.toString());
483         }
484
485         // First look up a member from the cache of calculated members
486
// (cubes and queries both have them).
487
switch (category) {
488         case Category.Member:
489         case Category.Unknown:
490             Member member = schemaReader.getCalculatedMember(names);
491             if (member != null) {
492                 return member;
493             }
494         }
495         // Likewise named set.
496
switch (category) {
497         case Category.Set:
498         case Category.Unknown:
499             NamedSet namedSet = schemaReader.getNamedSet(names);
500             if (namedSet != null) {
501                 return namedSet;
502             }
503         }
504
505         // Now resolve the name one part at a time.
506
for (int i = 0; i < names.length; i++) {
507             String JavaDoc name = names[i];
508             OlapElement child =
509                 schemaReader.getElementChild(parent, name, matchType);
510             // if we're doing a non-exact match, and we find a non-exact
511
// match, then for an after match, return the first child
512
// of each subsequent level; for a before match, return the
513
// last child
514
if (child != null && matchType != MatchType.EXACT &&
515                 !Util.equalName(child.getName(), name))
516             {
517                 Util.assertPrecondition(child instanceof Member);
518                 Member bestChild = (Member) child;
519                 for (int j = i + 1; j < names.length; j++) {
520                     Member[] children =
521                         schemaReader.getMemberChildren(bestChild);
522                     List<Member> childrenList = Arrays.asList(children);
523                     FunUtil.hierarchize(childrenList, false);
524                     if (matchType == MatchType.AFTER) {
525                         bestChild = childrenList.get(0);
526                     } else {
527                         bestChild =
528                             childrenList.get(children.length - 1);
529                     }
530                     if (bestChild == null) {
531                         child = null;
532                         break;
533                     }
534                 }
535                 parent = bestChild;
536                 break;
537             }
538             if (child == null) {
539                 if (LOGGER.isDebugEnabled()) {
540                     StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
541                     buf.append("Util.lookupCompound: ");
542                     buf.append("parent.name=");
543                     buf.append(parent.getName());
544                     buf.append(" has no child with name=");
545                     buf.append(name);
546                     LOGGER.debug(buf.toString());
547                 }
548
549                 if (failIfNotFound) {
550                     throw MondrianResource.instance().MdxChildObjectNotFound.ex(
551                         name, parent.getQualifiedName());
552                 } else {
553                     return null;
554                 }
555             }
556             parent = child;
557         }
558         if (LOGGER.isDebugEnabled()) {
559             StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
560             buf.append("Util.lookupCompound: ");
561             buf.append("found child.name=");
562             buf.append(parent.getName());
563             buf.append(", child.class=");
564             buf.append(parent.getClass().getName());
565             LOGGER.debug(buf.toString());
566         }
567
568         switch (category) {
569         case Category.Dimension:
570             if (parent instanceof Dimension) {
571                 return parent;
572             } else if (parent instanceof Hierarchy) {
573                 return parent.getDimension();
574             } else if (failIfNotFound) {
575                 throw Util.newError("Can not find dimension '" + implode(names) + "'");
576             } else {
577                 return null;
578             }
579         case Category.Hierarchy:
580             if (parent instanceof Hierarchy) {
581                 return parent;
582             } else if (parent instanceof Dimension) {
583                 return parent.getHierarchy();
584             } else if (failIfNotFound) {
585                 throw Util.newError("Can not find hierarchy '" + implode(names) + "'");
586             } else {
587                 return null;
588             }
589         case Category.Level:
590             if (parent instanceof Level) {
591                 return parent;
592             } else if (failIfNotFound) {
593                 throw Util.newError("Can not find level '" + implode(names) + "'");
594             } else {
595                 return null;
596             }
597         case Category.Member:
598             if (parent instanceof Member) {
599                 return parent;
600             } else if (failIfNotFound) {
601                 throw MondrianResource.instance().MdxCantFindMember.ex(implode(names));
602             } else {
603                 return null;
604             }
605         case Category.Unknown:
606             assertPostcondition(parent != null, "return != null");
607             return parent;
608         default:
609             throw newInternal("Bad switch " + category);
610         }
611     }
612
613     public static OlapElement lookup(Query q, String JavaDoc[] nameParts) {
614         final Exp exp = lookup(q, nameParts, false);
615         if (exp instanceof MemberExpr) {
616             MemberExpr memberExpr = (MemberExpr) exp;
617             return memberExpr.getMember();
618         } else if (exp instanceof LevelExpr) {
619             LevelExpr levelExpr = (LevelExpr) exp;
620             return levelExpr.getLevel();
621         } else if (exp instanceof HierarchyExpr) {
622             HierarchyExpr hierarchyExpr = (HierarchyExpr) exp;
623             return hierarchyExpr.getHierarchy();
624         } else if (exp instanceof DimensionExpr) {
625             DimensionExpr dimensionExpr = (DimensionExpr) exp;
626             return dimensionExpr.getDimension();
627         } else {
628             throw Util.newInternal("Not an olap element: " + exp);
629         }
630     }
631
632     /**
633      * Converts an identifier into an expression by resolving its parts into
634      * an OLAP object (dimension, hierarchy, level or member) within the
635      * context of a query.
636      *
637      * <p>If <code>allowProp</code> is true, also allows property references
638      * from valid members, for example
639      * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
640      * In this case, the result will be a {@link ResolvedFunCall}.
641      *
642      * @param q Query expression belongs to
643      * @param nameParts Parts of the identifier
644      * @param allowProp Whether to allow property references
645      * @return OLAP object or property reference
646      */

647     public static Exp lookup(
648             Query q, String JavaDoc[] nameParts, boolean allowProp) {
649
650         // First, look for a calculated member defined in the query.
651
final String JavaDoc fullName = quoteMdxIdentifier(nameParts);
652         // Look for any kind of object (member, level, hierarchy,
653
// dimension) in the cube. Use a schema reader without restrictions.
654
final SchemaReader schemaReader = q.getSchemaReader(false);
655         OlapElement olapElement = schemaReader.lookupCompound(
656             q.getCube(), nameParts, false, Category.Unknown);
657         if (olapElement != null) {
658             Role role = q.getConnection().getRole();
659             if (!role.canAccess(olapElement)) {
660                 olapElement = null;
661             }
662         }
663         if (olapElement == null) {
664             if (allowProp &&
665                     nameParts.length > 1) {
666                 String JavaDoc[] namePartsButOne = new String JavaDoc[nameParts.length - 1];
667                 System.arraycopy(nameParts, 0,
668                         namePartsButOne, 0,
669                         nameParts.length - 1);
670                 final String JavaDoc propertyName = nameParts[nameParts.length - 1];
671                 olapElement = schemaReader.lookupCompound(
672                         q.getCube(), namePartsButOne, false, Category.Member);
673                 if (olapElement != null &&
674                         isValidProperty((Member) olapElement, propertyName)) {
675                     return new UnresolvedFunCall(
676                             propertyName, Syntax.Property, new Exp[] {
677                                 createExpr(olapElement)});
678                 }
679             }
680             // if we're in the middle of loading the schema, the property has
681
// been set to ignore invalid members, and the member is
682
// non-existent, return the null member corresponding to the
683
// hierarchy of the element we're looking for; locate the
684
// hierarchy by incrementally truncating the name of the element
685
if (q.ignoreInvalidMembers()) {
686                 int nameLen = nameParts.length - 1;
687                 olapElement = null;
688                 while (nameLen > 0 && olapElement == null) {
689                     String JavaDoc[] partialName = new String JavaDoc[nameLen];
690                     System.arraycopy(
691                         nameParts,
692                         0,
693                         partialName,
694                         0,
695                         nameLen);
696                     olapElement = schemaReader.lookupCompound(
697                         q.getCube(), partialName, false, Category.Unknown);
698                     nameLen--;
699                 }
700                 if (olapElement != null) {
701                     olapElement = olapElement.getHierarchy().getNullMember();
702                 } else {
703                     throw MondrianResource.instance().MdxChildObjectNotFound.ex(
704                         fullName, q.getCube().getQualifiedName());
705                 }
706             } else {
707                 throw MondrianResource.instance().MdxChildObjectNotFound.ex(
708                     fullName, q.getCube().getQualifiedName());
709             }
710         }
711         // keep track of any measure members referenced; these will be used
712
// later to determine if cross joins on virtual cubes can be
713
// processed natively
714
q.addMeasuresMembers(olapElement);
715         return createExpr(olapElement);
716     }
717
718     /**
719      * Converts an olap element (dimension, hierarchy, level or member) into
720      * an expression representing a usage of that element in an MDX statement.
721      */

722     public static Exp createExpr(OlapElement element)
723     {
724         if (element instanceof Member) {
725             Member member = (Member) element;
726             return new MemberExpr(member);
727         } else if (element instanceof Level) {
728             Level level = (Level) element;
729             return new LevelExpr(level);
730         } else if (element instanceof Hierarchy) {
731             Hierarchy hierarchy = (Hierarchy) element;
732             return new HierarchyExpr(hierarchy);
733         } else if (element instanceof Dimension) {
734             Dimension dimension = (Dimension) element;
735             return new DimensionExpr(dimension);
736         } else if (element instanceof NamedSet) {
737             NamedSet namedSet = (NamedSet) element;
738             return new NamedSetExpr(namedSet);
739         } else {
740             throw Util.newInternal("Unexpected element type: " + element);
741         }
742     }
743
744     public static Member lookupHierarchyRootMember(
745         SchemaReader reader, Hierarchy hierarchy, String JavaDoc memberName)
746     {
747         return lookupHierarchyRootMember(
748             reader, hierarchy, memberName, MatchType.EXACT);
749     }
750
751     /**
752      * Finds a root member of a hierarchy with a given name.
753      *
754      * @param hierarchy
755      * @param memberName
756      * @return Member, or null if not found
757      */

758     public static Member lookupHierarchyRootMember(
759         SchemaReader reader,
760         Hierarchy hierarchy,
761         String JavaDoc memberName,
762         MatchType matchType)
763     {
764         // Lookup member at first level.
765
Member[] rootMembers = reader.getHierarchyRootMembers(hierarchy);
766
767         // if doing an inexact search on a non-all hieararchy, create
768
// a member corresponding to the name we're searching for so
769
// we can use it in a hierarchical search
770
Member searchMember = null;
771         if (matchType != MatchType.EXACT && !hierarchy.hasAll() &&
772             rootMembers.length > 0)
773         {
774             searchMember =
775                 hierarchy.createMember(
776                     null,
777                     rootMembers[0].getLevel(),
778                     memberName,
779                     null);
780         }
781
782         int bestMatch = -1;
783         for (int i = 0; i < rootMembers.length; i++) {
784             int rc;
785             // when searching on the ALL hierarchy, match must be exact
786
if (matchType == MatchType.EXACT || hierarchy.hasAll()) {
787                 rc = rootMembers[i].getName().compareToIgnoreCase(memberName);
788             } else {
789                 rc = FunUtil.compareSiblingMembers(
790                     rootMembers[i],
791                     searchMember);
792             }
793             if (rc == 0) {
794                 return rootMembers[i];
795             }
796             if (!hierarchy.hasAll()) {
797                 if (matchType == MatchType.BEFORE) {
798                     if (rc < 0 &&
799                         (bestMatch == -1 ||
800                         FunUtil.compareSiblingMembers(
801                             rootMembers[i],
802                             rootMembers[bestMatch]) > 0))
803                     {
804                         bestMatch = i;
805                     }
806                 } else if (matchType == MatchType.AFTER) {
807                     if (rc > 0 &&
808                         (bestMatch == -1 ||
809                         FunUtil.compareSiblingMembers(
810                             rootMembers[i],
811                             rootMembers[bestMatch]) < 0))
812                     {
813                         bestMatch = i;
814                     }
815                 }
816             }
817         }
818         if (matchType != MatchType.EXACT && bestMatch != -1) {
819             return rootMembers[bestMatch];
820         }
821         // If the first level is 'all', lookup member at second level. For
822
// example, they could say '[USA]' instead of '[(All
823
// Customers)].[USA]'.
824
return (rootMembers.length == 1 && rootMembers[0].isAll())
825             ? reader.lookupMemberChildByName(
826                 rootMembers[0],
827                 memberName,
828                 matchType)
829             : null;
830     }
831
832     /**
833      * Finds a named level in this hierarchy. Returns null if there is no
834      * such level.
835      */

836     public static Level lookupHierarchyLevel(Hierarchy hierarchy, String JavaDoc s) {
837         final Level[] levels = hierarchy.getLevels();
838         for (Level level : levels) {
839             if (level.getName().equalsIgnoreCase(s)) {
840                 return level;
841             }
842         }
843         return null;
844     }
845
846
847
848     /**
849      * Finds the zero based ordinal of a Member among its siblings.
850      */

851     public static int getMemberOrdinalInParent(SchemaReader reader,
852                                                Member member) {
853         Member parent = member.getParentMember();
854         Member[] siblings = (parent == null)
855             ? reader.getHierarchyRootMembers(member.getHierarchy())
856             : reader.getMemberChildren(parent);
857
858         for (int i = 0; i < siblings.length; i++) {
859             if (siblings[i] == member) {
860                 return i;
861             }
862         }
863         throw Util.newInternal(
864                 "could not find member " + member + " amongst its siblings");
865     }
866
867     /**
868      * returns the first descendant on the level underneath parent.
869      * If parent = [Time].[1997] and level = [Time].[Month], then
870      * the member [Time].[1997].[Q1].[1] will be returned
871      */

872     public static Member getFirstDescendantOnLevel(
873         SchemaReader reader,
874         Member parent,
875         Level level)
876     {
877         Member m = parent;
878         while (m.getLevel() != level) {
879             Member[] children = reader.getMemberChildren(m);
880             m = children[0];
881         }
882         return m;
883     }
884
885     /**
886      * Returns whether a string is null or empty.
887      */

888     public static boolean isEmpty(String JavaDoc s) {
889         return (s == null) || (s.length() == 0);
890     }
891
892     /**
893      * Encloses a value in single-quotes, to make a SQL string value. Examples:
894      * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
895      * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
896      */

897     public static String JavaDoc singleQuoteString(String JavaDoc val) {
898         StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
899         singleQuoteString(val, buf);
900         return buf.toString();
901     }
902
903     /**
904      * Encloses a value in single-quotes, to make a SQL string value. Examples:
905      * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
906      * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
907      */

908     public static void singleQuoteString(String JavaDoc val, StringBuilder JavaDoc buf) {
909         buf.append('\'');
910
911         String JavaDoc s0 = replace(val, "'", "''");
912         buf.append(s0);
913
914         buf.append('\'');
915     }
916
917     /**
918      * Creates a random number generator.
919      *
920      * @param seed Seed for random number generator.
921      * If 0, generate a seed from the system clock and print the value
922      * chosen. (This is effectively non-deterministic.)
923      * If -1, generate a seed from an internal random number generator.
924      * (This is deterministic, but ensures that different tests have
925      * different seeds.)
926      *
927      * @return A random number generator.
928      */

929     public static Random createRandom(long seed) {
930         if (seed == 0) {
931             seed = new Random().nextLong();
932             System.out.println("random: seed=" + seed);
933         } else if (seed == -1 && metaRandom != null) {
934             seed = metaRandom.nextLong();
935         }
936         return new Random(seed);
937     }
938
939     /**
940      * Returns whether a property is valid for a given member.
941      * It is valid if the property is defined at the member's level or at
942      * an ancestor level, or if the property is a standard property such as
943      * "FORMATTED_VALUE".
944      *
945      * @param member Member
946      * @param propertyName Property name
947      * @return Whether property is valid
948      */

949     public static boolean isValidProperty(
950             Member member, String JavaDoc propertyName) {
951         return lookupProperty(member.getLevel(), propertyName) != null;
952     }
953
954     /**
955      * Finds a member property called <code>propertyName</code> at, or above,
956      * <code>level</code>.
957      */

958     protected static Property lookupProperty(Level level, String JavaDoc propertyName) {
959         do {
960             Property[] properties = level.getProperties();
961             for (Property property : properties) {
962                 if (property.getName().equals(propertyName)) {
963                     return property;
964                 }
965             }
966             level = level.getParentLevel();
967         } while (level != null);
968         // Now try a standard property.
969
boolean caseSensitive =
970             MondrianProperties.instance().CaseSensitive.get();
971         final Property property = Property.lookup(propertyName, caseSensitive);
972         if (property != null &&
973                 property.isMemberProperty() &&
974                 property.isStandard()) {
975             return property;
976         }
977         return null;
978     }
979
980     /**
981      * Insert a call to this method if you want to flag a piece of
982      * undesirable code.
983      *
984      * @deprecated
985      */

986     public static void deprecated(String JavaDoc reason) {
987         throw new UnsupportedOperationException JavaDoc(reason);
988     }
989
990     public static Member[] addLevelCalculatedMembers(
991             SchemaReader reader,
992             Level level,
993             Member[] members) {
994         List<Member> calcMembers =
995             reader.getCalculatedMembers(level.getHierarchy());
996         List<Member> calcMembersInThisLevel = new ArrayList<Member>();
997         for (Member calcMember : calcMembers) {
998             if (calcMember.getLevel().equals(level)) {
999                 calcMembersInThisLevel.add(calcMember);
1000            }
1001        }
1002        if (!calcMembersInThisLevel.isEmpty()) {
1003            List<Member> newMemberList =
1004                new ArrayList<Member>(Arrays.asList(members));
1005            newMemberList.addAll(calcMembersInThisLevel);
1006            members = newMemberList.toArray(new Member[newMemberList.size()]);
1007        }
1008        return members;
1009    }
1010
1011    /**
1012     * Returns an exception which indicates that a particular piece of
1013     * functionality should work, but a developer has not implemented it yet.
1014     */

1015    public static RuntimeException JavaDoc needToImplement(Object JavaDoc o) {
1016        throw new UnsupportedOperationException JavaDoc("need to implement " + o);
1017    }
1018
1019    /**
1020     * Returns an exception indicating that we didn't expect to find this value
1021     * here.
1022     */

1023    public static <T extends Enum JavaDoc<T>> RuntimeException JavaDoc badValue(
1024        Enum JavaDoc<T> anEnum)
1025    {
1026        return Util.newInternal("Was not expecting value '" + anEnum +
1027            "' for enumeration '" + anEnum.getDeclaringClass().getName() +
1028            "' in this context");
1029    }
1030
1031    /**
1032     * Masks Mondrian's version number from a string.
1033     *
1034     * @param str String
1035     * @return String with each occurrence of mondrian's version number
1036     * (e.g. "2.3.0.0") replaced with "${mondrianVersion}"
1037     */

1038    public static String JavaDoc maskVersion(String JavaDoc str) {
1039        MondrianServer.MondrianVersion mondrianVersion =
1040            MondrianServer.forConnection(null).getVersion();
1041        String JavaDoc versionString = mondrianVersion.getVersionString();
1042        return replace(str, versionString, "${mondrianVersion}");
1043    }
1044
1045    public static class ErrorCellValue {
1046        public String JavaDoc toString() {
1047            return "#ERR";
1048        }
1049    }
1050
1051    /**
1052     * Throws an internal error if condition is not true. It would be called
1053     * <code>assert</code>, but that is a keyword as of JDK 1.4.
1054     */

1055    public static void assertTrue(boolean b) {
1056        if (!b) {
1057            throw newInternal("assert failed");
1058        }
1059    }
1060
1061    /**
1062     * Throws an internal error with the given messagee if condition is not
1063     * true. It would be called <code>assert</code>, but that is a keyword as
1064     * of JDK 1.4.
1065     */

1066    public static void assertTrue(boolean b, String JavaDoc message) {
1067        if (!b) {
1068            throw newInternal("assert failed: " + message);
1069        }
1070    }
1071
1072    /**
1073     * Creates an internal error with a given message.
1074     */

1075    public static RuntimeException JavaDoc newInternal(String JavaDoc message) {
1076        return MondrianResource.instance().Internal.ex(message);
1077    }
1078
1079    /**
1080     * Creates an internal error with a given message and cause.
1081     */

1082    public static RuntimeException JavaDoc newInternal(Throwable JavaDoc e, String JavaDoc message) {
1083        return MondrianResource.instance().Internal.ex(message, e);
1084    }
1085
1086    /**
1087     * Creates a non-internal error. Currently implemented in terms of
1088     * internal errors, but later we will create resourced messages.
1089     */

1090    public static RuntimeException JavaDoc newError(String JavaDoc message) {
1091        return newInternal(message);
1092    }
1093
1094    /**
1095     * Creates a non-internal error. Currently implemented in terms of
1096     * internal errors, but later we will create resourced messages.
1097     */

1098    public static RuntimeException JavaDoc newError(Throwable JavaDoc e, String JavaDoc message) {
1099        return newInternal(e, message);
1100    }
1101
1102    /**
1103     * Checks that a precondition (declared using the javadoc <code>@pre</code>
1104     * tag) is satisfied.
1105     *
1106     * @param b The value of executing the condition
1107     */

1108    public static void assertPrecondition(boolean b) {
1109        assertTrue(b);
1110    }
1111
1112    /**
1113     * Checks that a precondition (declared using the javadoc <code>@pre</code>
1114     * tag) is satisfied. For example,
1115     *
1116     * <blockquote><pre>void f(String s) {
1117     * Util.assertPrecondition(s != null, "s != null");
1118     * ...
1119     * }</pre></blockquote>
1120     *
1121     * @param b The value of executing the condition
1122     * @param condition The text of the condition
1123     */

1124    public static void assertPrecondition(boolean b, String JavaDoc condition) {
1125        assertTrue(b, condition);
1126    }
1127
1128    /**
1129     * Checks that a postcondition (declared using the javadoc
1130     * <code>@post</code> tag) is satisfied.
1131     *
1132     * @param b The value of executing the condition
1133     */

1134    public static void assertPostcondition(boolean b) {
1135        assertTrue(b);
1136    }
1137
1138    /**
1139     * Checks that a postcondition (declared using the javadoc
1140     * <code>@post</code> tag) is satisfied.
1141     *
1142     * @param b The value of executing the condition
1143     */

1144    public static void assertPostcondition(boolean b, String JavaDoc condition) {
1145        assertTrue(b, condition);
1146    }
1147
1148    /**
1149     * Converts an error into an array of strings, the most recent error first.
1150     *
1151     * @param e the error; may be null. Errors are chained according to their
1152     * {@link Throwable#getCause cause}.
1153     */

1154    public static String JavaDoc[] convertStackToString(Throwable JavaDoc e) {
1155        List<String JavaDoc> list = new ArrayList<String JavaDoc>();
1156        while (e != null) {
1157            String JavaDoc sMsg = getErrorMessage(e);
1158            list.add(sMsg);
1159            e = e.getCause();
1160        }
1161        return list.toArray(new String JavaDoc[list.size()]);
1162    }
1163
1164    /**
1165     * Constructs the message associated with an arbitrary Java error, making
1166     * up one based on the stack trace if there is none. As
1167     * {@link #getErrorMessage(Throwable,boolean)}, but does not print the
1168     * class name if the exception is derived from {@link java.sql.SQLException}
1169     * or is exactly a {@link java.lang.Exception}.
1170     */

1171    public static String JavaDoc getErrorMessage(Throwable JavaDoc err) {
1172        boolean prependClassName =
1173            !(err instanceof java.sql.SQLException JavaDoc ||
1174              err.getClass() == java.lang.Exception JavaDoc.class);
1175        return getErrorMessage(err, prependClassName);
1176    }
1177
1178    /**
1179     * Constructs the message associated with an arbitrary Java error, making
1180     * up one based on the stack trace if there is none.
1181     *
1182     * @param err the error
1183     * @param prependClassName should the error be preceded by the
1184     * class name of the Java exception? defaults to false, unless the error
1185     * is derived from {@link java.sql.SQLException} or is exactly a {@link
1186     * java.lang.Exception}
1187     */

1188    public static String JavaDoc getErrorMessage(
1189        Throwable JavaDoc err,
1190        boolean prependClassName)
1191    {
1192        String JavaDoc errMsg = err.getMessage();
1193        if ((errMsg == null) || (err instanceof RuntimeException JavaDoc)) {
1194            StringWriter JavaDoc sw = new StringWriter JavaDoc();
1195            PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1196            err.printStackTrace(pw);
1197            return sw.toString();
1198        } else {
1199            return (prependClassName)
1200                ? err.getClass().getName() + ": " + errMsg
1201                : errMsg;
1202
1203        }
1204    }
1205
1206    /**
1207     * Converts an expression to a string.
1208     */

1209    public static String JavaDoc unparse(Exp exp) {
1210        StringWriter JavaDoc sw = new StringWriter JavaDoc();
1211        PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1212        exp.unparse(pw);
1213        return sw.toString();
1214    }
1215
1216    /**
1217     * Converts an query to a string.
1218     */

1219    public static String JavaDoc unparse(Query query) {
1220        StringWriter JavaDoc sw = new StringWriter JavaDoc();
1221        PrintWriter JavaDoc pw = new QueryPrintWriter(sw);
1222        query.unparse(pw);
1223        return sw.toString();
1224    }
1225
1226    /**
1227     * Creates a file-protocol URL for the given file.
1228     */

1229    public static URL JavaDoc toURL(File JavaDoc file) throws MalformedURLException JavaDoc {
1230        String JavaDoc path = file.getAbsolutePath();
1231        // This is a bunch of weird code that is required to
1232
// make a valid URL on the Windows platform, due
1233
// to inconsistencies in what getAbsolutePath returns.
1234
String JavaDoc fs = System.getProperty("file.separator");
1235        if (fs.length() == 1) {
1236            char sep = fs.charAt(0);
1237            if (sep != '/') {
1238                path = path.replace(sep, '/');
1239            }
1240            if (path.charAt(0) != '/') {
1241                path = '/' + path;
1242            }
1243        }
1244        path = "file://" + path;
1245        return new URL JavaDoc(path);
1246    }
1247
1248    /**
1249     * <code>PropertyList</code> is an order-preserving list of key-value
1250     * pairs. Lookup is case-insensitive, but the case of keys is preserved.
1251     */

1252    public static class PropertyList {
1253        List<String JavaDoc[]> list = new ArrayList<String JavaDoc[]>();
1254
1255        public String JavaDoc get(String JavaDoc key) {
1256            return get(key, null);
1257        }
1258
1259        public String JavaDoc get(String JavaDoc key, String JavaDoc defaultValue) {
1260            for (int i = 0, n = list.size(); i < n; i++) {
1261                String JavaDoc[] pair = list.get(i);
1262                if (pair[0].equalsIgnoreCase(key)) {
1263                    return pair[1];
1264                }
1265            }
1266            return defaultValue;
1267        }
1268
1269        public String JavaDoc put(String JavaDoc key, String JavaDoc value) {
1270            for (int i = 0, n = list.size(); i < n; i++) {
1271                String JavaDoc[] pair = list.get(i);
1272                if (pair[0].equalsIgnoreCase(key)) {
1273                    String JavaDoc old = pair[1];
1274                    if (key.equalsIgnoreCase("Provider")) {
1275                        // Unlike all other properties, later values of
1276
// "Provider" do not supersede
1277
} else {
1278                        pair[1] = value;
1279                    }
1280                    return old;
1281                }
1282            }
1283            list.add(new String JavaDoc[] {key, value});
1284            return null;
1285        }
1286
1287        public String JavaDoc toString() {
1288            StringBuilder JavaDoc sb = new StringBuilder JavaDoc(64);
1289            for (int i = 0, n = list.size(); i < n; i++) {
1290                String JavaDoc[] pair = list.get(i);
1291                if (i > 0) {
1292                    sb.append("; ");
1293                }
1294                sb.append(pair[0]);
1295                sb.append('=');
1296
1297                if (pair[1] == null) {
1298                    sb.append("'null'");
1299                } else {
1300                    /*
1301                     * Quote a property value if is has a semi colon in it
1302                     * 'xxx;yyy';
1303                     */

1304                    if (pair[1].indexOf(';') >= 0 && pair[1].charAt(0) != '\'') {
1305                        sb.append("'");
1306                    }
1307
1308                    sb.append(pair[1]);
1309
1310                    if (pair[1].indexOf(';') >= 0 && pair[1].charAt(pair[1].length() - 1) != '\'') {
1311                        sb.append("'");
1312                    }
1313                }
1314
1315            }
1316            return sb.toString();
1317        }
1318
1319        public Iterator<String JavaDoc[]> iterator() {
1320            return list.iterator();
1321        }
1322    }
1323
1324    /**
1325     * Converts an OLE DB connect string into a {@link PropertyList}.
1326     *
1327     * <p> For example, <code>"Provider=MSOLAP; DataSource=LOCALHOST;"</code>
1328     * becomes the set of (key, value) pairs <code>{("Provider","MSOLAP"),
1329     * ("DataSource", "LOCALHOST")}</code>. Another example is
1330     * <code>Provider='sqloledb';Data Source='MySqlServer';Initial
1331     * Catalog='Pubs';Integrated Security='SSPI';</code>.
1332     *
1333     * <p> This method implements as much as possible of the <a
1334     * HREF="http://msdn.microsoft.com/library/en-us/oledb/htm/oledbconnectionstringsyntax.asp"
1335     * target="_blank">OLE DB connect string syntax
1336     * specification</a>. To find what it <em>actually</em> does, take
1337     * a look at the <code>mondrian.olap.UtilTestCase</code> test case.
1338     */

1339    public static PropertyList parseConnectString(String JavaDoc s) {
1340        return new ConnectStringParser(s).parse();
1341    }
1342
1343    private static class ConnectStringParser {
1344        private final String JavaDoc s;
1345        private final int n;
1346        private int i;
1347        private final StringBuilder JavaDoc nameBuf;
1348        private final StringBuilder JavaDoc valueBuf;
1349
1350        private ConnectStringParser(String JavaDoc s) {
1351            this.s = s;
1352            this.i = 0;
1353            this.n = s.length();
1354            this.nameBuf = new StringBuilder JavaDoc(64);
1355            this.valueBuf = new StringBuilder JavaDoc(64);
1356        }
1357
1358        PropertyList parse() {
1359            PropertyList list = new PropertyList();
1360            while (i < n) {
1361                parsePair(list);
1362            }
1363            return list;
1364        }
1365        /**
1366         * Reads "name=value;" or "name=value<EOF>".
1367         */

1368        void parsePair(PropertyList list) {
1369            String JavaDoc name = parseName();
1370            String JavaDoc value;
1371            if (i >= n) {
1372                value = "";
1373            } else if (s.charAt(i) == ';') {
1374                i++;
1375                value = "";
1376            } else {
1377                value = parseValue();
1378            }
1379            list.put(name, value);
1380        }
1381        /**
1382         * Reads "name=". Name can contain equals sign if equals sign is
1383         * doubled.
1384         */

1385        String JavaDoc parseName() {
1386            nameBuf.setLength(0);
1387            while (true) {
1388                char c = s.charAt(i);
1389                switch (c) {
1390                case '=':
1391                    i++;
1392                    if (i < n && (c = s.charAt(i)) == '=') {
1393                        // doubled equals sign; take one of them, and carry on
1394
i++;
1395                        nameBuf.append(c);
1396                        break;
1397                    }
1398                    String JavaDoc name = nameBuf.toString();
1399                    name = name.trim();
1400                    return name;
1401                case ' ':
1402                    if (nameBuf.length() == 0) {
1403                        // ignore preceding spaces
1404
i++;
1405                        break;
1406                    } else {
1407                        // fall through
1408
}
1409                default:
1410                    nameBuf.append(c);
1411                    i++;
1412                    if (i >= n) {
1413                        return nameBuf.toString().trim();
1414                    }
1415                }
1416            }
1417        }
1418        /**
1419         * Reads "value;" or "value<EOF>"
1420         */

1421        String JavaDoc parseValue() {
1422            char c;
1423            // skip over leading white space
1424
while ((c = s.charAt(i)) == ' ') {
1425                i++;
1426                if (i >= n) {
1427                    return "";
1428                }
1429            }
1430            if (c == '"' || c == '\'') {
1431                String JavaDoc value = parseQuoted(c);
1432                // skip over trailing white space
1433
while (i < n && (c = s.charAt(i)) == ' ') {
1434                    i++;
1435                }
1436                if (i >= n) {
1437                    return value;
1438                } else if (s.charAt(i) == ';') {
1439                    i++;
1440                    return value;
1441                } else {
1442                    throw new RuntimeException JavaDoc(
1443                            "quoted value ended too soon, at position " + i +
1444                            " in '" + s + "'");
1445                }
1446            } else {
1447                String JavaDoc value;
1448                int semi = s.indexOf(';', i);
1449                if (semi >= 0) {
1450                    value = s.substring(i, semi);
1451                    i = semi + 1;
1452                } else {
1453                    value = s.substring(i);
1454                    i = n;
1455                }
1456                return value.trim();
1457            }
1458        }
1459        /**
1460         * Reads a string quoted by a given character. Occurrences of the
1461         * quoting character must be doubled. For example,
1462         * <code>parseQuoted('"')</code> reads <code>"a ""new"" string"</code>
1463         * and returns <code>a "new" string</code>.
1464         */

1465        String JavaDoc parseQuoted(char q) {
1466            char c = s.charAt(i++);
1467            Util.assertTrue(c == q);
1468            valueBuf.setLength(0);
1469            while (i < n) {
1470                c = s.charAt(i);
1471                if (c == q) {
1472                    i++;
1473                    if (i < n) {
1474                        c = s.charAt(i);
1475                        if (c == q) {
1476                            valueBuf.append(c);
1477                            i++;
1478                            continue;
1479                        }
1480                    }
1481                    return valueBuf.toString();
1482                } else {
1483                    valueBuf.append(c);
1484                    i++;
1485                }
1486            }
1487            throw new RuntimeException JavaDoc(
1488                    "Connect string '" + s +
1489                    "' contains unterminated quoted value '" +
1490                    valueBuf.toString() + "'");
1491        }
1492    }
1493
1494    /**
1495     * Combines two integers into a hash code.
1496     */

1497    public static int hash(int i, int j) {
1498        return (i << 4) ^ j;
1499    }
1500
1501    /**
1502     * Computes a hash code from an existing hash code and an object (which
1503     * may be null).
1504     */

1505    public static int hash(int h, Object JavaDoc o) {
1506        int k = (o == null) ? 0 : o.hashCode();
1507        return ((h << 4) | h) ^ k;
1508    }
1509
1510    /**
1511     * Computes a hash code from an existing hash code and an array of objects
1512     * (which may be null).
1513     */

1514    public static int hashArray(int h, Object JavaDoc [] a) {
1515        // The hashcode for a null array and an empty array should be different
1516
// than h, so use magic numbers.
1517
if (a == null) {
1518            return hash(h, 19690429);
1519        }
1520        if (a.length == 0) {
1521            return hash(h, 19690721);
1522        }
1523        for (Object JavaDoc anA : a) {
1524            h = hash(h, anA);
1525        }
1526        return h;
1527    }
1528
1529    /**
1530     * Returns the cumulative amount of time spent accessing the database.
1531     */

1532    public static long dbTimeMillis() {
1533        return databaseMillis;
1534    }
1535
1536    /**
1537     * Adds to the cumulative amount of time spent accessing the database.
1538     */

1539    public static void addDatabaseTime(long millis) {
1540        databaseMillis += millis;
1541    }
1542
1543    /**
1544     * Returns the system time less the time spent accessing the database.
1545     * Use this method to figure out how long an operation took: call this
1546     * method before an operation and after an operation, and the difference
1547     * is the amount of non-database time spent.
1548     */

1549    public static long nonDbTimeMillis() {
1550        final long systemMillis = System.currentTimeMillis();
1551        return systemMillis - databaseMillis;
1552    }
1553
1554    /**
1555     * Creates a very simple implementation of {@link Validator}. (Only
1556     * useful for resolving trivial expressions.)
1557     */

1558    public static Validator createSimpleValidator(final FunTable funTable) {
1559        return new Validator() {
1560            public Query getQuery() {
1561                return null;
1562            }
1563
1564            public Exp validate(Exp exp, boolean scalar) {
1565                return exp;
1566            }
1567
1568            public void validate(ParameterExpr parameterExpr) {
1569            }
1570
1571            public void validate(MemberProperty memberProperty) {
1572            }
1573
1574            public void validate(QueryAxis axis) {
1575            }
1576
1577            public void validate(Formula formula) {
1578            }
1579
1580            public boolean canConvert(Exp fromExp, int to, int[] conversionCount) {
1581                return true;
1582            }
1583
1584            public boolean requiresExpression() {
1585                return false;
1586            }
1587
1588            public FunTable getFunTable() {
1589                return funTable;
1590            }
1591
1592            public Parameter createOrLookupParam(
1593                boolean definition,
1594                String JavaDoc name,
1595                Type type,
1596                Exp defaultExp,
1597                String JavaDoc description) {
1598                return null;
1599            }
1600        };
1601    }
1602
1603    /**
1604     * Read a Reader until EOF and return as String.
1605     * Note: this ought to be in a Utility class.
1606     *
1607     * @param rdr Reader to Read.
1608     * @param bufferSize size of buffer to allocate for reading.
1609     * @return content of Reader as String or null if Reader was empty.
1610     * @throws IOException
1611     */

1612    public static String JavaDoc readFully(final Reader JavaDoc rdr, final int bufferSize)
1613            throws IOException JavaDoc {
1614
1615        if (bufferSize <= 0) {
1616            throw new IllegalArgumentException JavaDoc(
1617                    "Buffer size must be greater than 0");
1618        }
1619
1620        final char[] buffer = new char[bufferSize];
1621        final StringBuilder JavaDoc buf = new StringBuilder JavaDoc(bufferSize);
1622
1623        int len = rdr.read(buffer);
1624        while (len != -1) {
1625            buf.append(buffer, 0, len);
1626            len = rdr.read(buffer);
1627        }
1628
1629        final String JavaDoc s = buf.toString();
1630        return (s.length() == 0) ? null : s;
1631    }
1632
1633
1634    /**
1635     * Read URL and return String containing content.
1636     *
1637     * @param urlStr actually a catalog URL
1638     * @return String containing content of catalog.
1639     * @throws MalformedURLException
1640     * @throws IOException
1641     */

1642    public static String JavaDoc readURL(final String JavaDoc urlStr)
1643            throws MalformedURLException JavaDoc, IOException JavaDoc {
1644        return readURL(urlStr, null);
1645    }
1646
1647    /**
1648     * Returns the contents of a URL, substituting tokens.
1649     *
1650     * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map.
1651     *
1652     * @param urlStr URL string
1653     * @param map Key/value map
1654     * @return Contents of URL with tokens substituted
1655     * @throws MalformedURLException
1656     * @throws IOException
1657     */

1658    public static String JavaDoc readURL(final String JavaDoc urlStr, Map map)
1659            throws MalformedURLException JavaDoc, IOException JavaDoc {
1660        final URL JavaDoc url = new URL JavaDoc(urlStr);
1661        return readURL(url, map);
1662    }
1663
1664    /**
1665     * Returns the contents of a URL.
1666     *
1667     * @param url URL
1668     * @return Contents of URL
1669     * @throws IOException
1670     */

1671    public static String JavaDoc readURL(final URL JavaDoc url) throws IOException JavaDoc {
1672        return readURL(url, null);
1673    }
1674
1675    /**
1676     * Returns the contents of a URL, substituting tokens.
1677     *
1678     * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map.
1679     *
1680     * @param url URL
1681     * @param map Key/value map
1682     * @return Contents of URL with tokens substituted
1683     * @throws IOException
1684     */

1685    public static String JavaDoc readURL(final URL JavaDoc url, Map<String JavaDoc, String JavaDoc> map) throws IOException JavaDoc {
1686        final Reader JavaDoc r =
1687            new BufferedReader JavaDoc(new InputStreamReader JavaDoc(url.openStream()));
1688        final int BUF_SIZE = 8096;
1689        try {
1690            String JavaDoc xmlCatalog = readFully(r, BUF_SIZE);
1691            if (map != null) {
1692                xmlCatalog = Util.replaceProperties(xmlCatalog, map);
1693            }
1694            return xmlCatalog;
1695        } finally {
1696            r.close();
1697        }
1698    }
1699
1700    public static Map<String JavaDoc, String JavaDoc> toMap(final Properties properties) {
1701        return new AbstractMap<String JavaDoc, String JavaDoc>() {
1702            public Set<Entry<String JavaDoc, String JavaDoc>> entrySet() {
1703                return (Set) properties.entrySet();
1704            }
1705        };
1706    }
1707    /**
1708     * Replaces tokens in a string.
1709     *
1710     * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map.
1711     * Otherwise "${key}" is left in the string unchanged.
1712     *
1713     * @param text Source string
1714     * @param env Map of key-value pairs
1715     * @return String with tokens substituted
1716     */

1717    public static String JavaDoc replaceProperties(
1718        String JavaDoc text,
1719        Map<String JavaDoc, String JavaDoc> env)
1720    {
1721        // As of JDK 1.5, cannot use StringBuilder - appendReplacement requires
1722
// the antediluvian StringBuffer.
1723
StringBuffer JavaDoc buf = new StringBuffer JavaDoc(text.length() + 200);
1724
1725        Pattern JavaDoc pattern = Pattern.compile("\\$\\{([^${}]+)\\}");
1726        Matcher JavaDoc matcher = pattern.matcher(text);
1727        while (matcher.find()) {
1728            String JavaDoc varName = matcher.group(1);
1729            String JavaDoc varValue = env.get(varName);
1730            if (varValue != null) {
1731                matcher.appendReplacement(buf, varValue);
1732            } else {
1733                matcher.appendReplacement(buf, "\\${$1}");
1734            }
1735        }
1736        matcher.appendTail(buf);
1737
1738        return buf.toString();
1739    }
1740
1741    public static String JavaDoc printMemory() {
1742        return printMemory(null);
1743    }
1744
1745    public static String JavaDoc printMemory(String JavaDoc msg) {
1746        final Runtime JavaDoc rt = Runtime.getRuntime();
1747        final long freeMemory = rt.freeMemory();
1748        final long totalMemory = rt.totalMemory();
1749        final StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
1750
1751        buf.append("FREE_MEMORY:");
1752        if (msg != null) {
1753            buf.append(msg);
1754            buf.append(':');
1755        }
1756        buf.append(' ');
1757        buf.append(freeMemory / 1024);
1758        buf.append("kb ");
1759
1760        long hundredths = (freeMemory * 10000) / totalMemory;
1761
1762        buf.append(hundredths / 100);
1763        hundredths %= 100;
1764        if (hundredths >= 10) {
1765            buf.append('.');
1766        } else {
1767            buf.append(".0");
1768        }
1769        buf.append(hundredths);
1770        buf.append('%');
1771
1772        return buf.toString();
1773    }
1774
1775    /**
1776     * Returns whether an enumeration value is a valid not-null value of a given
1777     * enumeration class.
1778     *
1779     * @param clazz Enumeration class
1780     * @param e Enumeration value
1781     * @return Whether t is a value of enum clazz
1782     */

1783    public static <E extends Enum JavaDoc<E>> boolean isValid(Class JavaDoc<E> clazz, E e) {
1784        E[] enumConstants = clazz.getEnumConstants();
1785        for (E enumConstant : enumConstants) {
1786            if (e == enumConstant) {
1787                return true;
1788            }
1789        }
1790        return false;
1791    }
1792
1793    /**
1794     * Looks up an enumeration by name, returns null if not valid.
1795     */

1796    public static <E extends Enum JavaDoc<E>> E lookup(Class JavaDoc<E> clazz, String JavaDoc name) {
1797        try {
1798            return Enum.valueOf(clazz, name);
1799        } catch (IllegalArgumentException JavaDoc e) {
1800            return null;
1801        }
1802    }
1803
1804    /**
1805     * Equivalent to {@link java.util.EnumSet#of(Enum, Enum[])} on JDK 1.5 or
1806     * later. Otherwise, returns an ordinary set.
1807     *
1808     * @param first an element that the set is to contain initially
1809     * @param rest the remaining elements the set is to contain initially
1810     * @throws NullPointerException if any of the specified elements are null,
1811     * or if <tt>rest</tt> is null
1812     * @return an enum set initially containing the specified elements
1813     */

1814    public static <E extends Enum JavaDoc<E>> Set<E> enumSetOf(E first, E... rest) {
1815        return compatible.enumSetOf(first, rest);
1816    }
1817
1818    /**
1819     * Equivalent to {@link java.util.EnumSet#noneOf(Class)} on JDK 1.5 or later.
1820     * Otherwise, returns an ordinary set.
1821     *
1822     * @param elementType the class object of the element type for this enum
1823     * set
1824     */

1825    public static <E extends Enum JavaDoc<E>> Set<E> enumSetNoneOf(Class JavaDoc<E> elementType) {
1826        return compatible.enumSetNoneOf(elementType);
1827    }
1828
1829    /**
1830     * Equivalent to {@link java.util.EnumSet#allOf(Class)} on JDK 1.5 or later.
1831     * Otherwise, returns an ordinary set.
1832
1833     * @param elementType the class object of the element type for this enum
1834     * set
1835     */

1836    public static <E extends Enum JavaDoc<E>> Set<E> enumSetAllOf(Class JavaDoc<E> elementType) {
1837        return compatible.enumSetAllOf(elementType);
1838    }
1839}
1840
1841// End Util.java
1842
Popular Tags