001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/Id.java#30 $
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/src/main/mondrian/olap/Id.java#30 $
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:
163                    //return name; Disabled to pass old tests...
164                case QUOTED:
165                    return "[" + name + "]";
166                case KEY:
167                    return "&[" + name + "]";
168                default:
169                    return "UNKNOWN:" + name;
170                }
171            }
172    
173            /**
174             * Appends this segment to a StringBuffer
175             *
176             * @param buf StringBuffer
177             */
178            public void toString(StringBuilder buf) {
179                switch (quoting) {
180                case UNQUOTED:
181                    buf.append(name);
182                    return;
183                case QUOTED:
184                    Util.quoteMdxIdentifier(name, buf);
185                    return;
186                case KEY:
187                    buf.append('&');
188                    Util.quoteMdxIdentifier(name, buf);
189                    return;
190                default:
191                    throw Util.unexpected(quoting);
192                }
193            }
194    
195            public boolean equals(final Object o) {
196                if (o instanceof Segment) {
197                    Segment that = (Segment) o;
198                    return that.name.equals(this.name) &&
199                        that.quoting == this.quoting;
200                } else {
201                    return false;
202                }
203            }
204    
205            public int hashCode() {
206                return name.hashCode();
207            }
208    
209            /**
210             * Converts an array of names to a list of segments.
211             *
212             * @param nameParts Array of names
213             * @return List of segments
214             */
215            public static List<Segment> toList(String... nameParts) {
216                final List<Segment> segments =
217                    new ArrayList<Segment>(nameParts.length);
218                for (String namePart : nameParts) {
219                    segments.add(new Segment(namePart, Id.Quoting.QUOTED));
220                }
221                return segments;
222            }
223    
224            /**
225             * Returns whether this segment matches a given name according to
226             * the rules of case-sensitivity and quoting.
227             *
228             * @param name Name to match
229             * @return Whether matches
230             */
231            public boolean matches(String name) {
232                switch (quoting) {
233                case UNQUOTED:
234                    return Util.equalName(this.name, name);
235                case QUOTED:
236                    return this.name.equals(name);
237                default:
238                    return false;
239                }
240            }
241        }
242    
243        public enum Quoting {
244    
245            /**
246             * Unquoted identifier, for example "Measures".
247             */
248            UNQUOTED,
249    
250            /**
251             * Quoted identifier, for example "[Measures]".
252             */
253            QUOTED,
254    
255            /**
256             * Identifier quoted with an ampersand to indicate a key value, for
257             * example the second segment in "[Employees].&[89]".
258             */
259            KEY
260        }
261    }
262    
263    // End Id.java