001/*
002 * Copyright 2011-2016 UnboundID Corp.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018package com.unboundid.scim.sdk;
019
020import com.unboundid.scim.schema.CoreSchema;
021
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025
026
027/**
028 * This class represents a path to an attribute or sub-attribute. The path is
029 * a string comprising an schema URI (the SCIM core schema is assumed
030 * if absent), an attribute name, and an optional sub-attribute name.
031 */
032public class AttributePath
033{
034  /**
035   * A regular expression to match the components of an attribute path.
036   * Given the path "urn:scim:schemas:core:user:1.0:name.familyName" then
037   * group 2 will match "urn:scim:schemas:core:user:1.0", group 3 matches
038   * "name" and group 5 matches "familyName".
039   */
040  private static final Pattern pattern =
041      Pattern.compile("^((.+):)?([^.]+)(\\.(.+))?$");
042
043  /**
044   * The URI of the attribute schema.
045   */
046  private final String attributeSchema;
047
048  /**
049   * The name of the attribute.
050   */
051  private final String attributeName;
052
053  /**
054   * The name of the sub-attribute, or {@code null} if absent.
055   */
056  private final String subAttributeName;
057
058
059
060  /**
061   * Create a new attribute path.
062   *
063   * @param attributeSchema   The URI of the attribute schema.
064   * @param attributeName     The name of the attribute.
065   * @param subAttributeName  The name of the sub-attribute, or {@code null} if
066   *                          absent.
067   */
068  public AttributePath(final String attributeSchema,
069                       final String attributeName,
070                       final String subAttributeName)
071  {
072    this.attributeSchema  = attributeSchema;
073    this.attributeName    = attributeName;
074    this.subAttributeName = subAttributeName;
075  }
076
077
078
079  /**
080   * Parse an attribute path.
081   *
082   * @param path  The attribute path.
083   *
084   * @return The parsed attribute path.
085   */
086  public static AttributePath parse(final String path)
087  {
088    final Matcher matcher = pattern.matcher(path);
089
090    if (!matcher.matches() || matcher.groupCount() != 5)
091    {
092      throw new IllegalArgumentException(
093          String.format(
094              "'%s' does not match '[schema:]attr[.sub-attr]' format", path));
095    }
096
097    final String attributeSchema = matcher.group(2);
098    final String attributeName = matcher.group(3);
099    final String subAttributeName = matcher.group(5);
100
101    if (attributeSchema != null)
102    {
103      return new AttributePath(attributeSchema, attributeName,
104                               subAttributeName);
105    }
106    else
107    {
108      return new AttributePath(SCIMConstants.SCHEMA_URI_CORE, attributeName,
109                               subAttributeName);
110    }
111  }
112
113
114
115  /**
116   * Parse an attribute path.
117   *
118   * @param path  The attribute path.
119   * @param defaultSchema The default schema to assume for attributes that do
120   *                      not have the schema part of the urn specified. The
121   *                      'id', 'externalId', and 'meta' attributes will always
122   *                      assume the SCIM Core schema.
123   *
124   * @return The parsed attribute path.
125   */
126  public static AttributePath parse(final String path,
127                                    final String defaultSchema)
128  {
129    final Matcher matcher = pattern.matcher(path);
130
131    if (!matcher.matches() || matcher.groupCount() != 5)
132    {
133      throw new IllegalArgumentException(
134              String.format(
135                "'%s' does not match '[schema:]attr[.sub-attr]' format", path));
136    }
137
138    final String attributeSchema = matcher.group(2);
139    final String attributeName = matcher.group(3);
140    final String subAttributeName = matcher.group(5);
141
142    if (attributeSchema != null)
143    {
144      return new AttributePath(attributeSchema, attributeName,
145              subAttributeName);
146    }
147    else
148    {
149      if (attributeName.equalsIgnoreCase(
150                  CoreSchema.ID_DESCRIPTOR.getName()) ||
151          attributeName.equalsIgnoreCase(
152                  CoreSchema.EXTERNAL_ID_DESCRIPTOR.getName()) ||
153          attributeName.equalsIgnoreCase(
154                  CoreSchema.META_DESCRIPTOR.getName()))
155      {
156        return new AttributePath(SCIMConstants.SCHEMA_URI_CORE, attributeName,
157                subAttributeName);
158      }
159      else
160      {
161        return new AttributePath(defaultSchema, attributeName,
162                subAttributeName);
163      }
164    }
165  }
166
167
168
169  /**
170   * {@inheritDoc}
171   */
172  @Override
173  public String toString()
174  {
175    final StringBuilder builder = new StringBuilder();
176    toString(builder);
177    return builder.toString();
178  }
179
180
181
182  /**
183   * Append the string representation of the attribute path to the provided
184   * buffer.
185   *
186   * @param builder  The buffer to which the string representation of the
187   *                 attribute path is to be appended.
188   */
189  public void toString(final StringBuilder builder)
190  {
191    if (!attributeSchema.equalsIgnoreCase(SCIMConstants.SCHEMA_URI_CORE))
192    {
193      builder.append(attributeSchema);
194      builder.append(':');
195    }
196
197    builder.append(attributeName);
198    if (subAttributeName != null)
199    {
200      builder.append('.');
201      builder.append(subAttributeName);
202    }
203  }
204
205
206
207  /**
208   * Retrieve the URI of the attribute schema.
209   * @return The URI of the attribute schema.
210   */
211  public String getAttributeSchema()
212  {
213    return attributeSchema;
214  }
215
216
217
218  /**
219   * Retrieve the name of the attribute.
220   * @return The name of the attribute.
221   */
222  public String getAttributeName()
223  {
224    return attributeName;
225  }
226
227
228
229  /**
230   * Retrieve the name of the sub-attribute, or {@code null} if absent.
231   * @return The name of the sub-attribute, or {@code null} if absent.
232   */
233  public String getSubAttributeName()
234  {
235    return subAttributeName;
236  }
237}