001    /*
002    // $Id: //open/mondrian-release/3.0/src/main/mondrian/olap/Id.java#2 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 1998-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2007 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 21 January, 1999
012    */
013    
014    package mondrian.olap;
015    import mondrian.olap.type.Type;
016    import mondrian.mdx.MdxVisitor;
017    
018    import java.io.PrintWriter;
019    import java.util.List;
020    import java.util.ArrayList;
021    import java.util.Collections;
022    
023    /**
024     * Multi-part identifier.
025     *
026     * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/olap/Id.java#2 $
027     */
028    public class Id
029        extends ExpBase
030        implements Cloneable {
031    
032        private final List<Segment> segments;
033    
034        /**
035         * Creates an identifier containing a single part.
036         *
037         * @param segment Segment, consisting of a name and quoting style
038         */
039        public Id(Segment segment) {
040            segments = Collections.singletonList(segment);
041        }
042    
043        public Id(List<Segment> segments) {
044            this.segments = segments;
045        }
046    
047        public Id clone() {
048            // This is immutable, so no need to clone.
049            return this;
050        }
051    
052        public int getCategory() {
053            return Category.Unknown;
054        }
055    
056        public Type getType() {
057            // Can't give the type until we have resolved.
058            throw new UnsupportedOperationException();
059        }
060    
061        public String toString() {
062            final StringBuilder buf = new StringBuilder();
063            Util.quoteMdxIdentifier(segments, buf);
064            return buf.toString();
065        }
066    
067        public String[] toStringArray() {
068            String[] names = new String[segments.size()];
069            int k = 0;
070            for (Segment segment : segments) {
071                names[k++] = segment.name;
072            }
073            return names;
074        }
075    
076        public List<Segment> getSegments() {
077            return Collections.unmodifiableList(this.segments);
078        }
079    
080        public Id.Segment getElement(int i) {
081            return segments.get(i);
082        }
083    
084        /**
085         * Returns a new Identifier consisting of this one with another segment
086         * appended. Does not modify this Identifier.
087         *
088         * @param segment Name of segment
089         * @return New identifier
090         */
091        public Id append(Segment segment) {
092            List<Segment> newSegments = new ArrayList<Segment>(segments);
093            newSegments.add(segment);
094            return new Id(newSegments);
095        }
096    
097        public Exp accept(Validator validator) {
098            if (segments.size() == 1) {
099                final Segment s = segments.get(0);
100                if (s.quoting == Quoting.UNQUOTED &&
101                    validator.getFunTable().isReserved(s.name)) {
102                    return Literal.createSymbol(s.name.toUpperCase());
103                }
104            }
105            final Exp element =
106                Util.lookup(
107                    validator.getQuery(), segments, true);
108    
109            if (element == null) {
110                return null;
111            }
112            return element.accept(validator);
113        }
114    
115        public Object accept(MdxVisitor visitor) {
116            return visitor.visit(this);
117        }
118    
119        public void unparse(PrintWriter pw) {
120            int k = 0;
121            for (Segment s : segments) {
122                if (k++ > 0) {
123                    pw.print(".");
124                }
125                switch (s.quoting) {
126                case UNQUOTED:
127                    pw.print(s.name);
128                    break;
129                case KEY:
130                    pw.print("&[" + Util.mdxEncodeString(s.name) + "]");
131                    break;
132                case QUOTED:
133                    pw.print("[" + Util.mdxEncodeString(s.name) + "]");
134                    break;
135                }
136            }
137        }
138    
139        /**
140         * Component in a compound identifier. It is described by its name and how
141         * the name is quoted.
142         *
143         * <p>For example, the identifier
144         * <code>[Store].USA.[New Mexico].&[45]</code> has four segments:<ul>
145         * <li>"Store", {@link mondrian.olap.Id.Quoting#QUOTED}</li>
146         * <li>"USA", {@link mondrian.olap.Id.Quoting#UNQUOTED}</li>
147         * <li>"New Mexico", {@link mondrian.olap.Id.Quoting#QUOTED}</li>
148         * <li>"45", {@link mondrian.olap.Id.Quoting#KEY}</li>
149         * </ul>
150         */
151        public static class Segment {
152            public final String name;
153            public final Quoting quoting;
154    
155            public Segment(String name, Quoting quoting) {
156                this.name = name;
157                this.quoting = quoting;
158            }
159    
160            public String toString() {
161                switch (quoting) {
162                case UNQUOTED: //return name; Disabled to pass old tests...
163                case QUOTED: return "[" + name + "]";
164                case KEY: return "&[" + name + "]";
165                default: return "UNKNOWN:" + name;
166                }
167            }
168    
169            /**
170             * Appends this segment to a StringBuffer
171             *
172             * @param buf StringBuffer
173             */
174            public void toString(StringBuilder buf) {
175                switch (quoting) {
176                case UNQUOTED:
177                    buf.append(name);
178                    return;
179                case QUOTED:
180                    Util.quoteMdxIdentifier(name, buf);
181                    return;
182                case KEY:
183                    buf.append('&');
184                    Util.quoteMdxIdentifier(name, buf);
185                    return;
186                default:
187                    throw Util.unexpected(quoting);
188                }
189            }
190    
191            public boolean equals(final Object o) {
192                if (o instanceof Segment) {
193                    Segment that = (Segment) o;
194                    return that.name.equals(this.name) &&
195                        that.quoting == this.quoting;
196                } else {
197                    return false;
198                }
199            }
200    
201            public int hashCode() {
202                return name.hashCode();
203            }
204    
205            /**
206             * Converts an array of names to a list of segments.
207             *
208             * @param nameParts Array of names
209             * @return List of segments
210             */
211            public static List<Segment> toList(String... nameParts) {
212                final List<Segment> segments =
213                    new ArrayList<Segment>(nameParts.length);
214                for (String namePart : nameParts) {
215                    segments.add(new Segment(namePart, Id.Quoting.QUOTED));
216                }
217                return segments;
218            }
219    
220            /**
221             * Returns whether this segment matches a given name according to
222             * the rules of case-sensitivity and quoting.
223             *
224             * @param name Name to match
225             * @return Whether matches
226             */
227            public boolean matches(String name) {
228                switch (quoting) {
229                case UNQUOTED:
230                    return Util.equalName(this.name, name);
231                case QUOTED:
232                    return this.name.equals(name);
233                default:
234                    return false;
235                }
236            }
237        }
238    
239        public enum Quoting {
240    
241            /**
242             * Unquoted identifier, for example "Measures".
243             */
244            UNQUOTED,
245    
246            /**
247             * Quoted identifier, for example "[Measures]".
248             */
249            QUOTED,
250    
251            /**
252             * Identifier quoted with an ampersand to indicate a key value, for
253             * example the second segment in "[Employees].&[89]".
254             */
255            KEY
256        }
257    }
258    
259    // End Id.java