001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 1998-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap;
012
013import mondrian.mdx.MdxVisitor;
014import mondrian.olap.type.Type;
015
016import org.olap4j.impl.UnmodifiableArrayList;
017
018import java.io.PrintWriter;
019import java.util.*;
020
021/**
022 * Multi-part identifier.
023 *
024 * @author jhyde, 21 January, 1999
025 */
026public class Id
027    extends ExpBase
028    implements Cloneable
029{
030
031    private final List<Segment> segments;
032
033    /**
034     * Creates an identifier containing a single part.
035     *
036     * @param segment Segment, consisting of a name and quoting style
037     */
038    public Id(Segment segment) {
039        segments = Collections.singletonList(segment);
040    }
041
042    public Id(List<Segment> segments) {
043        this.segments = segments;
044        if (segments.size() <= 0) {
045            throw new IllegalArgumentException();
046        }
047    }
048
049    public Id clone() {
050        // This is immutable, so no need to clone.
051        return this;
052    }
053
054    public int getCategory() {
055        return Category.Unknown;
056    }
057
058    public Type getType() {
059        // Can't give the type until we have resolved.
060        throw new UnsupportedOperationException();
061    }
062
063    public String toString() {
064        final StringBuilder buf = new StringBuilder();
065        Util.quoteMdxIdentifier(segments, buf);
066        return buf.toString();
067    }
068
069    public String[] toStringArray() {
070        String[] names = new String[segments.size()];
071        int k = 0;
072        for (Segment segment : segments) {
073            names[k++] = ((NameSegment) segment).getName();
074        }
075        return names;
076    }
077
078    public List<Segment> getSegments() {
079        return Collections.unmodifiableList(this.segments);
080    }
081
082    public Id.Segment getElement(int i) {
083        return segments.get(i);
084    }
085
086    /**
087     * Returns a new Identifier consisting of this one with another segment
088     * appended. Does not modify this Identifier.
089     *
090     * @param segment Name of segment
091     * @return New identifier
092     */
093    public Id append(Segment segment) {
094        List<Segment> newSegments = new ArrayList<Segment>(segments);
095        newSegments.add(segment);
096        return new Id(newSegments);
097    }
098
099    public Exp accept(Validator validator) {
100        if (segments.size() == 1) {
101            final Segment s = segments.get(0);
102            if (s.quoting == Quoting.UNQUOTED) {
103                NameSegment nameSegment = (NameSegment) s;
104                if (validator.getFunTable().isReserved(nameSegment.getName())) {
105                    return Literal.createSymbol(
106                        nameSegment.getName().toUpperCase());
107                }
108            }
109        }
110        final Exp element =
111            Util.lookup(
112                validator.getQuery(),
113                validator.getSchemaReader().withLocus(),
114                segments,
115                true);
116
117        if (element == null) {
118            return null;
119        }
120        return element.accept(validator);
121    }
122
123    public Object accept(MdxVisitor visitor) {
124        return visitor.visit(this);
125    }
126
127    public void unparse(PrintWriter pw) {
128        pw.print(toString());
129    }
130
131    /**
132     * Component in a compound identifier. It is described by its name and how
133     * the name is quoted.
134     *
135     * <p>For example, the identifier
136     * <code>[Store].USA.[New Mexico].&[45]</code> has four segments:<ul>
137     * <li>"Store", {@link mondrian.olap.Id.Quoting#QUOTED}</li>
138     * <li>"USA", {@link mondrian.olap.Id.Quoting#UNQUOTED}</li>
139     * <li>"New Mexico", {@link mondrian.olap.Id.Quoting#QUOTED}</li>
140     * <li>"45", {@link mondrian.olap.Id.Quoting#KEY}</li>
141     * </ul>
142     */
143    public static abstract class Segment {
144        public final Quoting quoting;
145
146        protected Segment(Quoting quoting) {
147            this.quoting = quoting;
148        }
149
150        public String toString() {
151            final StringBuilder buf = new StringBuilder();
152            toString(buf);
153            return buf.toString();
154        }
155
156        public Quoting getQuoting() {
157            return quoting;
158        }
159
160        public abstract List<NameSegment> getKeyParts();
161
162        /**
163         * Converts an array of names to a list of segments.
164         *
165         * @param nameParts Array of names
166         * @return List of segments
167         */
168        public static List<Segment> toList(String... nameParts) {
169            final List<Segment> segments =
170                new ArrayList<Segment>(nameParts.length);
171            for (String namePart : nameParts) {
172                segments.add(new NameSegment(namePart));
173            }
174            return segments;
175        }
176
177        /**
178         * Returns whether this segment matches a given name according to
179         * the rules of case-sensitivity and quoting.
180         *
181         * @param name Name to match
182         * @return Whether matches
183         */
184        public abstract boolean matches(String name);
185
186        /**
187         * Appends this segment to a StringBuilder.
188         *
189         * @param buf String builder to write to
190         */
191        public abstract void toString(StringBuilder buf);
192    }
193
194    /**
195     * Component in a compound identifier that describes the name of an object.
196     * Optionally, the name is quoted in brackets.
197     *
198     * @see KeySegment
199     */
200    public static class NameSegment extends Segment {
201        public final String name;
202
203        /**
204         * Creates a name segment with the given quoting.
205         *
206         * @param name Name
207         * @param quoting Quoting style
208         */
209        public NameSegment(String name, Quoting quoting) {
210            super(quoting);
211            this.name = name;
212            if (name == null) {
213                throw new NullPointerException();
214            }
215            if (!(quoting == Quoting.QUOTED || quoting == Quoting.UNQUOTED)) {
216                throw new IllegalArgumentException();
217            }
218        }
219
220        /**
221         * Creates a quoted name segment.
222         *
223         * @param name Name
224         */
225        public NameSegment(String name) {
226            this(name, Quoting.QUOTED);
227        }
228
229        public boolean equals(final Object o) {
230            if (this == o) {
231                return true;
232            }
233            if (!(o instanceof NameSegment)) {
234                return false;
235            }
236            NameSegment that = (NameSegment) o;
237            return that.name.equals(this.name);
238        }
239
240        public int hashCode() {
241            return name.hashCode();
242        }
243
244        public String getName() {
245            return name;
246        }
247
248        public List<NameSegment> getKeyParts() {
249            return null;
250        }
251
252        public void toString(StringBuilder buf) {
253            switch (quoting) {
254            case UNQUOTED:
255                buf.append(name);
256                return;
257            case QUOTED:
258                Util.quoteMdxIdentifier(name, buf);
259                return;
260            default:
261                throw Util.unexpected(quoting);
262            }
263        }
264
265        public boolean matches(String name) {
266            switch (quoting) {
267            case UNQUOTED:
268                return Util.equalName(this.name, name);
269            case QUOTED:
270                return Util.equalName(this.name, name);
271            default:
272                return false;
273            }
274        }
275    }
276
277    /**
278     * Identifier segment representing a key, possibly composite.
279     */
280    public static class KeySegment extends Segment {
281        public final List<NameSegment> subSegmentList;
282
283        /**
284         * Creates a KeySegment with one or more sub-segments.
285         *
286         * @param subSegments Array of sub-segments
287         */
288        public KeySegment(NameSegment... subSegments) {
289            super(Quoting.KEY);
290            if (subSegments.length < 1) {
291                throw new IllegalArgumentException();
292            }
293            this.subSegmentList = UnmodifiableArrayList.asCopyOf(subSegments);
294        }
295
296        /**
297         * Creates a KeySegment a list of sub-segments.
298         *
299         * @param subSegmentList List of sub-segments
300         */
301        public KeySegment(List<NameSegment> subSegmentList) {
302            super(Quoting.KEY);
303            if (subSegmentList.size() < 1) {
304                throw new IllegalArgumentException();
305            }
306            this.subSegmentList =
307                new UnmodifiableArrayList<NameSegment>(
308                    subSegmentList.toArray(
309                        new NameSegment[subSegmentList.size()]));
310        }
311
312        public boolean equals(final Object o) {
313            if (this == o) {
314                return true;
315            }
316            if (!(o instanceof KeySegment)) {
317                return false;
318            }
319            KeySegment that = (KeySegment) o;
320            return this.subSegmentList.equals(that.subSegmentList);
321        }
322
323        public int hashCode() {
324            return subSegmentList.hashCode();
325        }
326
327        public void toString(StringBuilder buf) {
328            for (NameSegment segment : subSegmentList) {
329                buf.append('&');
330                segment.toString(buf);
331            }
332        }
333
334        public List<NameSegment> getKeyParts() {
335            return subSegmentList;
336        }
337
338        public boolean matches(String name) {
339            return false;
340        }
341    }
342
343    public enum Quoting {
344
345        /**
346         * Unquoted identifier, for example "Measures".
347         */
348        UNQUOTED,
349
350        /**
351         * Quoted identifier, for example "[Measures]".
352         */
353        QUOTED,
354
355        /**
356         * Identifier quoted with an ampersand to indicate a key value, for
357         * example the second segment in "[Employees].&amp;[89]".
358         */
359        KEY
360    }
361}
362
363// End Id.java